/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

/*****************************************************************************
 * name:		be_ai_move.c
 *
 * desc:		bot movement AI
 *
 * $Archive: /code/botlib/be_ai_move.c $
 *
 *****************************************************************************/

#include "../game/q_shared.h"
#include "l_memory.h"
#include "l_libvar.h"
#include "l_utils.h"
#include "l_script.h"
#include "l_precomp.h"
#include "l_struct.h"
#include "aasfile.h"
#include "../game/botlib.h"
#include "../game/be_aas.h"
#include "be_aas_funcs.h"
#include "be_interface.h"

#include "../game/be_ea.h"
#include "../game/be_ai_goal.h"
#include "../game/be_ai_move.h"


//#define DEBUG_AI_MOVE
//#define DEBUG_ELEVATOR
//#define DEBUG_GRAPPLE

// bk001204 - redundant bot_avoidspot_t, see ../game/be_ai_move.h

//movement state
//NOTE: the moveflags MFL_ONGROUND, MFL_TELEPORTED, MFL_WATERJUMP and
//		MFL_GRAPPLEPULL must be set outside the movement code
typedef struct bot_movestate_s
{
    //input vars (all set outside the movement code)
    vec3_t origin;								//origin of the bot
    vec3_t velocity;							//velocity of the bot
    vec3_t viewoffset;							//view offset
    int entitynum;								//entity number of the bot
    int client;									//client number of the bot
    float thinktime;							//time the bot thinks
    int presencetype;							//presencetype of the bot
    vec3_t viewangles;							//view angles of the bot
    //state vars
    int areanum;								//area the bot is in
    int lastareanum;							//last area the bot was in
    int lastgoalareanum;						//last goal area number
    int lastreachnum;							//last reachability number
    vec3_t lastorigin;							//origin previous cycle
    int reachareanum;							//area number of the reachabilty
    int moveflags;								//movement flags
    int jumpreach;								//set when jumped
    float grapplevisible_time;					//last time the grapple was visible
    float lastgrappledist;						//last distance to the grapple end
    float reachability_time;					//time to use current reachability
    int avoidreach[MAX_AVOIDREACH];				//reachabilities to avoid
    float avoidreachtimes[MAX_AVOIDREACH];		//times to avoid the reachabilities
    int avoidreachtries[MAX_AVOIDREACH];		//number of tries before avoiding
    //
    bot_avoidspot_t avoidspots[MAX_AVOIDSPOTS];	//spots to avoid
    int numavoidspots;
} bot_movestate_t;

//used to avoid reachability links for some time after being used
#define AVOIDREACH
#define AVOIDREACH_TIME			6		//avoid links for 6 seconds after use
#define AVOIDREACH_TRIES		4
//prediction times
#define PREDICTIONTIME_JUMP	3		//in seconds
#define PREDICTIONTIME_MOVE	2		//in seconds
//weapon indexes for weapon jumping
#define WEAPONINDEX_ROCKET_LAUNCHER		5
#define WEAPONINDEX_BFG					9

#define MODELTYPE_FUNC_PLAT		1
#define MODELTYPE_FUNC_BOB		2
#define MODELTYPE_FUNC_DOOR		3
#define MODELTYPE_FUNC_STATIC	4

libvar_t *sv_maxstep;
libvar_t *sv_maxbarrier;
libvar_t *sv_gravity;
libvar_t *weapindex_rocketlauncher;
libvar_t *weapindex_bfg10k;
libvar_t *weapindex_grapple;
libvar_t *entitytypemissile;
libvar_t *offhandgrapple;
libvar_t *cmd_grappleoff;
libvar_t *cmd_grappleon;
//type of model, func_plat or func_bobbing
int modeltypes[MAX_MODELS];

bot_movestate_t *botmovestates[MAX_CLIENTS+1];

//========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//========================================================================
int BotAllocMoveState(void)
{
    int i;

    for (i = 1; i <= MAX_CLIENTS; i++)
    {
        if (!botmovestates[i])
        {
            botmovestates[i] = (bot_movestate_t *)GetClearedMemory(sizeof(bot_movestate_t));
            return i;
        } //end if
    } //end for
    return 0;
} //end of the function BotAllocMoveState
//========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//========================================================================
void BotFreeMoveState(int handle)
{
    if (handle <= 0 || handle > MAX_CLIENTS)
    {
        botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle);
        return;
    } //end if
    if (!botmovestates[handle])
    {
        botimport.Print(PRT_FATAL, "invalid move state %d\n", handle);
        return;
    } //end if
    FreeMemory(botmovestates[handle]);
    botmovestates[handle] = NULL;
} //end of the function BotFreeMoveState
//========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//========================================================================
bot_movestate_t *BotMoveStateFromHandle(int handle)
{
    if (handle <= 0 || handle > MAX_CLIENTS)
    {
        botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle);
        return NULL;
    } //end if
    if (!botmovestates[handle])
    {
        botimport.Print(PRT_FATAL, "invalid move state %d\n", handle);
        return NULL;
    } //end if
    return botmovestates[handle];
} //end of the function BotMoveStateFromHandle
//========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//========================================================================
void BotInitMoveState(int handle, bot_initmove_t *initmove)
{
    bot_movestate_t *ms;

    ms = BotMoveStateFromHandle(handle);
    if (!ms) return;
    VectorCopy(initmove->origin, ms->origin);
    VectorCopy(initmove->velocity, ms->velocity);
    VectorCopy(initmove->viewoffset, ms->viewoffset);
    ms->entitynum = initmove->entitynum;
    ms->client = initmove->client;
    ms->thinktime = initmove->thinktime;
    ms->presencetype = initmove->presencetype;
    VectorCopy(initmove->viewangles, ms->viewangles);
    //
    ms->moveflags &= ~MFL_ONGROUND;
    if (initmove->or_moveflags & MFL_ONGROUND) ms->moveflags |= MFL_ONGROUND;
    ms->moveflags &= ~MFL_TELEPORTED;
    if (initmove->or_moveflags & MFL_TELEPORTED) ms->moveflags |= MFL_TELEPORTED;
    ms->moveflags &= ~MFL_WATERJUMP;
    if (initmove->or_moveflags & MFL_WATERJUMP) ms->moveflags |= MFL_WATERJUMP;
    ms->moveflags &= ~MFL_WALK;
    if (initmove->or_moveflags & MFL_WALK) ms->moveflags |= MFL_WALK;
    ms->moveflags &= ~MFL_GRAPPLEPULL;
    if (initmove->or_moveflags & MFL_GRAPPLEPULL) ms->moveflags |= MFL_GRAPPLEPULL;
} //end of the function BotInitMoveState
//========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//========================================================================
float AngleDiff(float ang1, float ang2)
{
    float diff;

    diff = ang1 - ang2;
    if (ang1 > ang2)
    {
        if (diff > 180.0) diff -= 360.0;
    } //end if
    else
    {
        if (diff < -180.0) diff += 360.0;
    } //end else
    return diff;
} //end of the function AngleDiff
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotFuzzyPointReachabilityArea(vec3_t origin)
{
    int firstareanum, j, x, y, z;
    int areas[10], numareas, areanum, bestareanum;
    float dist, bestdist;
    vec3_t points[10], v, end;

    firstareanum = 0;
    areanum = AAS_PointAreaNum(origin);
    if (areanum)
    {
        firstareanum = areanum;
        if (AAS_AreaReachability(areanum)) return areanum;
    } //end if
    VectorCopy(origin, end);
    end[2] += 4;
    numareas = AAS_TraceAreas(origin, end, areas, points, 10);
    for (j = 0; j < numareas; j++)
    {
        if (AAS_AreaReachability(areas[j])) return areas[j];
    } //end for
    bestdist = 999999;
    bestareanum = 0;
    for (z = 1; z >= -1; z -= 1)
    {
        for (x = 1; x >= -1; x -= 1)
        {
            for (y = 1; y >= -1; y -= 1)
            {
                VectorCopy(origin, end);
                end[0] += x * 8;
                end[1] += y * 8;
                end[2] += z * 12;
                numareas = AAS_TraceAreas(origin, end, areas, points, 10);
                for (j = 0; j < numareas; j++)
                {
                    if (AAS_AreaReachability(areas[j]))
                    {
                        VectorSubtract(points[j], origin, v);
                        dist = VectorLength(v);
                        if (dist < bestdist)
                        {
                            bestareanum = areas[j];
                            bestdist = dist;
                        } //end if
                    } //end if
                    if (!firstareanum) firstareanum = areas[j];
                } //end for
            } //end for
        } //end for
        if (bestareanum) return bestareanum;
    } //end for
    return firstareanum;
} //end of the function BotFuzzyPointReachabilityArea
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotReachabilityArea(vec3_t origin, int client)
{
    int modelnum, modeltype, reachnum, areanum;
    aas_reachability_t reach;
    vec3_t org, end, mins, maxs, up = {0, 0, 1};
    bsp_trace_t bsptrace;
    aas_trace_t trace;

    //check if the bot is standing on something
    AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs);
    VectorMA(origin, -3, up, end);
    bsptrace = AAS_Trace(origin, mins, maxs, end, client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
    if (!bsptrace.startsolid && bsptrace.fraction < 1 && bsptrace.ent != ENTITYNUM_NONE)
    {
        //if standing on the world the bot should be in a valid area
        if (bsptrace.ent == ENTITYNUM_WORLD)
        {
            return BotFuzzyPointReachabilityArea(origin);
        } //end if

        modelnum = AAS_EntityModelindex(bsptrace.ent);
        modeltype = modeltypes[modelnum];

        //if standing on a func_plat or func_bobbing then the bot is assumed to be
        //in the area the reachability points to
        if (modeltype == MODELTYPE_FUNC_PLAT || modeltype == MODELTYPE_FUNC_BOB)
        {
            reachnum = AAS_NextModelReachability(0, modelnum);
            if (reachnum)
            {
                AAS_ReachabilityFromNum(reachnum, &reach);
                return reach.areanum;
            } //end if
        } //end else if

        //if the bot is swimming the bot should be in a valid area
        if (AAS_Swimming(origin))
        {
            return BotFuzzyPointReachabilityArea(origin);
        } //end if
        //
        areanum = BotFuzzyPointReachabilityArea(origin);
        //if the bot is in an area with reachabilities
        if (areanum && AAS_AreaReachability(areanum)) return areanum;
        //trace down till the ground is hit because the bot is standing on some other entity
        VectorCopy(origin, org);
        VectorCopy(org, end);
        end[2] -= 800;
        trace = AAS_TraceClientBBox(org, end, PRESENCE_CROUCH, -1);
        if (!trace.startsolid)
        {
            VectorCopy(trace.endpos, org);
        } //end if
        //
        return BotFuzzyPointReachabilityArea(org);
    } //end if
    //
    return BotFuzzyPointReachabilityArea(origin);
} //end of the function BotReachabilityArea
//===========================================================================
// returns the reachability area the bot is in
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
/*
int BotReachabilityArea(vec3_t origin, int testground)
{
	int firstareanum, i, j, x, y, z;
	int areas[10], numareas, areanum, bestareanum;
	float dist, bestdist;
	vec3_t org, end, points[10], v;
	aas_trace_t trace;

	firstareanum = 0;
	for (i = 0; i < 2; i++)
	{
		VectorCopy(origin, org);
		//if test at the ground (used when bot is standing on an entity)
		if (i > 0)
		{
			VectorCopy(origin, end);
			end[2] -= 800;
			trace = AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1);
			if (!trace.startsolid)
			{
				VectorCopy(trace.endpos, org);
			} //end if
		} //end if

		firstareanum = 0;
		areanum = AAS_PointAreaNum(org);
		if (areanum)
		{
			firstareanum = areanum;
			if (AAS_AreaReachability(areanum)) return areanum;
		} //end if
		bestdist = 999999;
		bestareanum = 0;
		for (z = 1; z >= -1; z -= 1)
		{
			for (x = 1; x >= -1; x -= 1)
			{
				for (y = 1; y >= -1; y -= 1)
				{
					VectorCopy(org, end);
					end[0] += x * 8;
					end[1] += y * 8;
					end[2] += z * 12;
					numareas = AAS_TraceAreas(org, end, areas, points, 10);
					for (j = 0; j < numareas; j++)
					{
						if (AAS_AreaReachability(areas[j]))
						{
							VectorSubtract(points[j], org, v);
							dist = VectorLength(v);
							if (dist < bestdist)
							{
								bestareanum = areas[j];
								bestdist = dist;
							} //end if
						} //end if
					} //end for
				} //end for
			} //end for
			if (bestareanum) return bestareanum;
		} //end for
		if (!testground) break;
	} //end for
//#ifdef DEBUG
	//botimport.Print(PRT_MESSAGE, "no reachability area\n");
//#endif //DEBUG
	return firstareanum;
} //end of the function BotReachabilityArea*/
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotOnMover(vec3_t origin, int entnum, aas_reachability_t *reach)
{
    int i, modelnum;
    vec3_t mins, maxs, modelorigin, org, end;
    vec3_t angles = {0, 0, 0};
    vec3_t boxmins = {-16, -16, -8}, boxmaxs = {16, 16, 8};
    bsp_trace_t trace;

    modelnum = reach->facenum & 0x0000FFFF;
    //get some bsp model info
    AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL);
    //
    if (!AAS_OriginOfMoverWithModelNum(modelnum, modelorigin))
    {
        botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum);
        return qfalse;
    } //end if
    //
    for (i = 0; i < 2; i++)
    {
        if (origin[i] > modelorigin[i] + maxs[i] + 16) return qfalse;
        if (origin[i] < modelorigin[i] + mins[i] - 16) return qfalse;
    } //end for
    //
    VectorCopy(origin, org);
    org[2] += 24;
    VectorCopy(origin, end);
    end[2] -= 48;
    //
    trace = AAS_Trace(org, boxmins, boxmaxs, end, entnum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
    if (!trace.startsolid && !trace.allsolid)
    {
        //NOTE: the reachability face number is the model number of the elevator
        if (trace.ent != ENTITYNUM_NONE && AAS_EntityModelNum(trace.ent) == modelnum)
        {
            return qtrue;
        } //end if
    } //end if
    return qfalse;
} //end of the function BotOnMover
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int MoverDown(aas_reachability_t *reach)
{
    int modelnum;
    vec3_t mins, maxs, origin;
    vec3_t angles = {0, 0, 0};

    modelnum = reach->facenum & 0x0000FFFF;
    //get some bsp model info
    AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin);
    //
    if (!AAS_OriginOfMoverWithModelNum(modelnum, origin))
    {
        botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum);
        return qfalse;
    } //end if
    //if the top of the plat is below the reachability start point
    if (origin[2] + maxs[2] < reach->start[2]) return qtrue;
    return qfalse;
} //end of the function MoverDown
//========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//========================================================================
void BotSetBrushModelTypes(void)
{
    int ent, modelnum;
    char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY];

    Com_Memset(modeltypes, 0, MAX_MODELS * sizeof(int));
    //
    for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent))
    {
        if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue;
        if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) continue;
        if (model[0]) modelnum = atoi(model+1);
        else modelnum = 0;

        if (modelnum < 0 || modelnum > MAX_MODELS)
        {
            botimport.Print(PRT_MESSAGE, "entity %s model number out of range\n", classname);
            continue;
        } //end if

        if (!Q_stricmp(classname, "func_bobbing"))
            modeltypes[modelnum] = MODELTYPE_FUNC_BOB;
        else if (!Q_stricmp(classname, "func_plat"))
            modeltypes[modelnum] = MODELTYPE_FUNC_PLAT;
        else if (!Q_stricmp(classname, "func_door"))
            modeltypes[modelnum] = MODELTYPE_FUNC_DOOR;
        else if (!Q_stricmp(classname, "func_static"))
            modeltypes[modelnum] = MODELTYPE_FUNC_STATIC;
    } //end for
} //end of the function BotSetBrushModelTypes
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotOnTopOfEntity(bot_movestate_t *ms)
{
    vec3_t mins, maxs, end, up = {0, 0, 1};
    bsp_trace_t trace;

    AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs);
    VectorMA(ms->origin, -3, up, end);
    trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
    if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) )
    {
        return trace.ent;
    } //end if
    return -1;
} //end of the function BotOnTopOfEntity
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotValidTravel(vec3_t origin, aas_reachability_t *reach, int travelflags)
{
    //if the reachability uses an unwanted travel type
    if (AAS_TravelFlagForType(reach->traveltype) & ~travelflags) return qfalse;
    //don't go into areas with bad travel types
    if (AAS_AreaContentsTravelFlags(reach->areanum) & ~travelflags) return qfalse;
    return qtrue;
} //end of the function BotValidTravel
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotAddToAvoidReach(bot_movestate_t *ms, int number, float avoidtime)
{
    int i;

    for (i = 0; i < MAX_AVOIDREACH; i++)
    {
        if (ms->avoidreach[i] == number)
        {
            if (ms->avoidreachtimes[i] > AAS_Time()) ms->avoidreachtries[i]++;
            else ms->avoidreachtries[i] = 1;
            ms->avoidreachtimes[i] = AAS_Time() + avoidtime;
            return;
        } //end if
    } //end for
    //add the reachability to the reachabilities to avoid for a while
    for (i = 0; i < MAX_AVOIDREACH; i++)
    {
        if (ms->avoidreachtimes[i] < AAS_Time())
        {
            ms->avoidreach[i] = number;
            ms->avoidreachtimes[i] = AAS_Time() + avoidtime;
            ms->avoidreachtries[i] = 1;
            return;
        } //end if
    } //end for
} //end of the function BotAddToAvoidReach
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
float DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2)
{
    vec3_t proj, dir;
    int j;

    AAS_ProjectPointOntoVector(p, lp1, lp2, proj);
    for (j = 0; j < 3; j++)
        if ((proj[j] > lp1[j] && proj[j] > lp2[j]) ||
                (proj[j] < lp1[j] && proj[j] < lp2[j]))
            break;
    if (j < 3) {
        if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j]))
            VectorSubtract(p, lp1, dir);
        else
            VectorSubtract(p, lp2, dir);
        return VectorLengthSquared(dir);
    }
    VectorSubtract(p, proj, dir);
    return VectorLengthSquared(dir);
} //end of the function DistanceFromLineSquared
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
float VectorDistanceSquared(vec3_t p1, vec3_t p2)
{
    vec3_t dir;
    VectorSubtract(p2, p1, dir);
    return VectorLengthSquared(dir);
} //end of the function VectorDistanceSquared
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotAvoidSpots(vec3_t origin, aas_reachability_t *reach, bot_avoidspot_t *avoidspots, int numavoidspots)
{
    int checkbetween, i, type;
    float squareddist, squaredradius;

    switch(reach->traveltype & TRAVELTYPE_MASK)
    {
    case TRAVEL_WALK: checkbetween = qtrue; break;
    case TRAVEL_CROUCH: checkbetween = qtrue; break;
    case TRAVEL_BARRIERJUMP: checkbetween = qtrue; break;
    case TRAVEL_LADDER: checkbetween = qtrue; break;
    case TRAVEL_WALKOFFLEDGE: checkbetween = qfalse; break;
    case TRAVEL_JUMP: checkbetween = qfalse; break;
    case TRAVEL_SWIM: checkbetween = qtrue; break;
    case TRAVEL_WATERJUMP: checkbetween = qtrue; break;
    case TRAVEL_TELEPORT: checkbetween = qfalse; break;
    case TRAVEL_ELEVATOR: checkbetween = qfalse; break;
    case TRAVEL_GRAPPLEHOOK: checkbetween = qfalse; break;
    case TRAVEL_ROCKETJUMP: checkbetween = qfalse; break;
    case TRAVEL_BFGJUMP: checkbetween = qfalse; break;
    case TRAVEL_JUMPPAD: checkbetween = qfalse; break;
    case TRAVEL_FUNCBOB: checkbetween = qfalse; break;
    default: checkbetween = qtrue; break;
    } //end switch

    type = AVOID_CLEAR;
    for (i = 0; i < numavoidspots; i++)
    {
        squaredradius = Square(avoidspots[i].radius);
        squareddist = DistanceFromLineSquared(avoidspots[i].origin, origin, reach->start);
        // if moving towards the avoid spot
        if (squareddist < squaredradius &&
                VectorDistanceSquared(avoidspots[i].origin, origin) > squareddist)
        {
            type = avoidspots[i].type;
        } //end if
        else if (checkbetween) {
            squareddist = DistanceFromLineSquared(avoidspots[i].origin, reach->start, reach->end);
            // if moving towards the avoid spot
            if (squareddist < squaredradius &&
                    VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist)
            {
                type = avoidspots[i].type;
            } //end if
        } //end if
        else
        {
            VectorDistanceSquared(avoidspots[i].origin, reach->end);
            // if the reachability leads closer to the avoid spot
            if (squareddist < squaredradius &&
                    VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist)
            {
                type = avoidspots[i].type;
            } //end if
        } //end else
        if (type == AVOID_ALWAYS)
            return type;
    } //end for
    return type;
} //end of the function BotAvoidSpots
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type)
{
    bot_movestate_t *ms;

    ms = BotMoveStateFromHandle(movestate);
    if (!ms) return;
    if (type == AVOID_CLEAR)
    {
        ms->numavoidspots = 0;
        return;
    } //end if

    if (ms->numavoidspots >= MAX_AVOIDSPOTS)
        return;
    VectorCopy(origin, ms->avoidspots[ms->numavoidspots].origin);
    ms->avoidspots[ms->numavoidspots].radius = radius;
    ms->avoidspots[ms->numavoidspots].type = type;
    ms->numavoidspots++;
} //end of the function BotAddAvoidSpot
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotGetReachabilityToGoal(vec3_t origin, int areanum,
                             int lastgoalareanum, int lastareanum,
                             int *avoidreach, float *avoidreachtimes, int *avoidreachtries,
                             bot_goal_t *goal, int travelflags, int movetravelflags,
                             struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags)
{
    int i, t, besttime, bestreachnum, reachnum;
    aas_reachability_t reach;

    //if not in a valid area
    if (!areanum) return 0;
    //
    if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goal->areanum))
    {
        travelflags |= TFL_DONOTENTER;
        movetravelflags |= TFL_DONOTENTER;
    } //end if
    //use the routing to find the next area to go to
    besttime = 0;
    bestreachnum = 0;
    //
    for (reachnum = AAS_NextAreaReachability(areanum, 0); reachnum;
            reachnum = AAS_NextAreaReachability(areanum, reachnum))
    {
#ifdef AVOIDREACH
        //check if it isn't an reachability to avoid
        for (i = 0; i < MAX_AVOIDREACH; i++)
        {
            if (avoidreach[i] == reachnum && avoidreachtimes[i] >= AAS_Time()) break;
        } //end for
        if (i != MAX_AVOIDREACH && avoidreachtries[i] > AVOIDREACH_TRIES)
        {
#ifdef DEBUG
            if (bot_developer->integer)
            {
                botimport.Print(PRT_MESSAGE, "avoiding reachability %d\n", avoidreach[i]);
            } //end if
#endif //DEBUG
            continue;
        } //end if
#endif //AVOIDREACH
        //get the reachability from the number
        AAS_ReachabilityFromNum(reachnum, &reach);
        //NOTE: do not go back to the previous area if the goal didn't change
        //NOTE: is this actually avoidance of local routing minima between two areas???
        if (lastgoalareanum == goal->areanum && reach.areanum == lastareanum) continue;
        //if (AAS_AreaContentsTravelFlags(reach.areanum) & ~travelflags) continue;
        //if the travel isn't valid
        if (!BotValidTravel(origin, &reach, movetravelflags)) continue;
        //get the travel time
        t = AAS_AreaTravelTimeToGoalArea(reach.areanum, reach.end, goal->areanum, travelflags);
        //if the goal area isn't reachable from the reachable area
        if (!t) continue;
        //if the bot should not use this reachability to avoid bad spots
        if (BotAvoidSpots(origin, &reach, avoidspots, numavoidspots)) {
            if (flags) {
                *flags |= MOVERESULT_BLOCKEDBYAVOIDSPOT;
            }
            continue;
        }
        //add the travel time towards the area
        t += reach.traveltime;// + AAS_AreaTravelTime(areanum, origin, reach.start);
        //if the travel time is better than the ones already found
        if (!besttime || t < besttime)
        {
            besttime = t;
            bestreachnum = reachnum;
        } //end if
    } //end for
    //
    return bestreachnum;
} //end of the function BotGetReachabilityToGoal
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BotAddToTarget(vec3_t start, vec3_t end, float maxdist, float *dist, vec3_t target)
{
    vec3_t dir;
    float curdist;

    VectorSubtract(end, start, dir);
    curdist = VectorNormalize(dir);
    if (*dist + curdist < maxdist)
    {
        VectorCopy(end, target);
        *dist += curdist;
        return qfalse;
    } //end if
    else
    {
        VectorMA(start, maxdist - *dist, dir, target);
        *dist = maxdist;
        return qtrue;
    } //end else
} //end of the function BotAddToTarget

int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target)
{
    aas_reachability_t reach;
    int reachnum, lastareanum;
    bot_movestate_t *ms;
    vec3_t end;
    float dist;

    ms = BotMoveStateFromHandle(movestate);
    if (!ms) return qfalse;
    reachnum = 0;
    //if the bot has no goal or no last reachability
    if (!ms->lastreachnum || !goal) return qfalse;

    reachnum = ms->lastreachnum;
    VectorCopy(ms->origin, end);
    lastareanum = ms->lastareanum;
    dist = 0;
    while(reachnum && dist < lookahead)
    {
        AAS_ReachabilityFromNum(reachnum, &reach);
        if (BotAddToTarget(end, reach.start, lookahead, &dist, target)) return qtrue;
        //never look beyond teleporters
        if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_TELEPORT) return qtrue;
        //never look beyond the weapon jump point
        if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ROCKETJUMP) return qtrue;
        if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_BFGJUMP) return qtrue;
        //don't add jump pad distances
        if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_JUMPPAD &&
                (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR &&
                (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB)
        {
            if (BotAddToTarget(reach.start, reach.end, lookahead, &dist, target)) return qtrue;
        } //end if
        reachnum = BotGetReachabilityToGoal(reach.end, reach.areanum,
                                            ms->lastgoalareanum, lastareanum,
                                            ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries,
                                            goal, travelflags, travelflags, NULL, 0, NULL);
        VectorCopy(reach.end, end);
        lastareanum = reach.areanum;
        if (lastareanum == goal->areanum)
        {
            BotAddToTarget(reach.end, goal->origin, lookahead, &dist, target);
            return qtrue;
        } //end if
    } //end while
    //
    return qfalse;
} //end of the function BotMovementViewTarget
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotVisible(int ent, vec3_t eye, vec3_t target)
{
    bsp_trace_t trace;

    trace = AAS_Trace(eye, NULL, NULL, target, ent, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
    if (trace.fraction >= 1) return qtrue;
    return qfalse;
} //end of the function BotVisible
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target)
{
    aas_reachability_t reach;
    int reachnum, lastgoalareanum, lastareanum, i;
    int avoidreach[MAX_AVOIDREACH];
    float avoidreachtimes[MAX_AVOIDREACH];
    int avoidreachtries[MAX_AVOIDREACH];
    vec3_t end;

    //if the bot has no goal or no last reachability
    if (!goal) return qfalse;
    //if the areanum is not valid
    if (!areanum) return qfalse;
    //if the goal areanum is not valid
    if (!goal->areanum) return qfalse;

    Com_Memset(avoidreach, 0, MAX_AVOIDREACH * sizeof(int));
    lastgoalareanum = goal->areanum;
    lastareanum = areanum;
    VectorCopy(origin, end);
    //only do 20 hops
    for (i = 0; i < 20 && (areanum != goal->areanum); i++)
    {
        //
        reachnum = BotGetReachabilityToGoal(end, areanum,
                                            lastgoalareanum, lastareanum,
                                            avoidreach, avoidreachtimes, avoidreachtries,
                                            goal, travelflags, travelflags, NULL, 0, NULL);
        if (!reachnum) return qfalse;
        AAS_ReachabilityFromNum(reachnum, &reach);
        //
        if (BotVisible(goal->entitynum, goal->origin, reach.start))
        {
            VectorCopy(reach.start, target);
            return qtrue;
        } //end if
        //
        if (BotVisible(goal->entitynum, goal->origin, reach.end))
        {
            VectorCopy(reach.end, target);
            return qtrue;
        } //end if
        //
        if (reach.areanum == goal->areanum)
        {
            VectorCopy(reach.end, target);
            return qtrue;
        } //end if
        //
        lastareanum = areanum;
        areanum = reach.areanum;
        VectorCopy(reach.end, end);
        //
    } //end while
    //
    return qfalse;
} //end of the function BotPredictVisiblePosition
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void MoverBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter)
{
    int modelnum;
    vec3_t mins, maxs, origin, mids;
    vec3_t angles = {0, 0, 0};

    modelnum = reach->facenum & 0x0000FFFF;
    //get some bsp model info
    AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin);
    //
    if (!AAS_OriginOfMoverWithModelNum(modelnum, origin))
    {
        botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum);
    } //end if
    //get a point just above the plat in the bottom position
    VectorAdd(mins, maxs, mids);
    VectorMA(origin, 0.5, mids, bottomcenter);
    bottomcenter[2] = reach->start[2];
} //end of the function MoverBottomCenter
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum)
{
    float dist, startz;
    vec3_t start, end;
    aas_trace_t trace;

    //do gap checking
    startz = origin[2];
    //this enables walking down stairs more fluidly
    {
        VectorCopy(origin, start);
        VectorCopy(origin, end);
        end[2] -= 60;
        trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum);
        if (trace.fraction >= 1) return 1;
        startz = trace.endpos[2] + 1;
    }
    //
    for (dist = 8; dist <= 100; dist += 8)
    {
        VectorMA(origin, dist, hordir, start);
        start[2] = startz + 24;
        VectorCopy(start, end);
        end[2] -= 48 + sv_maxbarrier->value;
        trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum);
        //if solid is found the bot can't walk any further and fall into a gap
        if (!trace.startsolid)
        {
            //if it is a gap
            if (trace.endpos[2] < startz - sv_maxstep->value - 8)
            {
                VectorCopy(trace.endpos, end);
                end[2] -= 20;
                if (AAS_PointContents(end) & CONTENTS_WATER) break;
                //if a gap is found slow down
                //botimport.Print(PRT_MESSAGE, "gap at %f\n", dist);
                return dist;
            } //end if
            startz = trace.endpos[2];
        } //end if
    } //end for
    return 0;
} //end of the function BotGapDistance
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotCheckBarrierJump(bot_movestate_t *ms, vec3_t dir, float speed)
{
    vec3_t start, hordir, end;
    aas_trace_t trace;

    VectorCopy(ms->origin, end);
    end[2] += sv_maxbarrier->value;
    //trace right up
    trace = AAS_TraceClientBBox(ms->origin, end, PRESENCE_NORMAL, ms->entitynum);
    //this shouldn't happen... but we check anyway
    if (trace.startsolid) return qfalse;
    //if very low ceiling it isn't possible to jump up to a barrier
    if (trace.endpos[2] - ms->origin[2] < sv_maxstep->value) return qfalse;
    //
    hordir[0] = dir[0];
    hordir[1] = dir[1];
    hordir[2] = 0;
    VectorNormalize(hordir);
    VectorMA(ms->origin, ms->thinktime * speed * 0.5, hordir, end);
    VectorCopy(trace.endpos, start);
    end[2] = trace.endpos[2];
    //trace from previous trace end pos horizontally in the move direction
    trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum);
    //again this shouldn't happen
    if (trace.startsolid) return qfalse;
    //
    VectorCopy(trace.endpos, start);
    VectorCopy(trace.endpos, end);
    end[2] = ms->origin[2];
    //trace down from the previous trace end pos
    trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum);
    //if solid
    if (trace.startsolid) return qfalse;
    //if no obstacle at all
    if (trace.fraction >= 1.0) return qfalse;
    //if less than the maximum step height
    if (trace.endpos[2] - ms->origin[2] < sv_maxstep->value) return qfalse;
    //
    EA_Jump(ms->client);
    EA_Move(ms->client, hordir, speed);
    ms->moveflags |= MFL_BARRIERJUMP;
    //there is a barrier
    return qtrue;
} //end of the function BotCheckBarrierJump
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotSwimInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type)
{
    vec3_t normdir;

    VectorCopy(dir, normdir);
    VectorNormalize(normdir);
    EA_Move(ms->client, normdir, speed);
    return qtrue;
} //end of the function BotSwimInDirection
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotWalkInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type)
{
    vec3_t hordir, cmdmove, velocity, tmpdir, origin;
    int presencetype, maxframes, cmdframes, stopevent;
    aas_clientmove_t move;
    float dist;

    if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND;
    //if the bot is on the ground
    if (ms->moveflags & MFL_ONGROUND)
    {
        //if there is a barrier the bot can jump on
        if (BotCheckBarrierJump(ms, dir, speed)) return qtrue;
        //remove barrier jump flag
        ms->moveflags &= ~MFL_BARRIERJUMP;
        //get the presence type for the movement
        if ((type & MOVE_CROUCH) && !(type & MOVE_JUMP)) presencetype = PRESENCE_CROUCH;
        else presencetype = PRESENCE_NORMAL;
        //horizontal direction
        hordir[0] = dir[0];
        hordir[1] = dir[1];
        hordir[2] = 0;
        VectorNormalize(hordir);
        //if the bot is not supposed to jump
        if (!(type & MOVE_JUMP))
        {
            //if there is a gap, try to jump over it
            if (BotGapDistance(ms->origin, hordir, ms->entitynum) > 0) type |= MOVE_JUMP;
        } //end if
        //get command movement
        VectorScale(hordir, speed, cmdmove);
        VectorCopy(ms->velocity, velocity);
        //
        if (type & MOVE_JUMP)
        {
            //botimport.Print(PRT_MESSAGE, "trying jump\n");
            cmdmove[2] = 400;
            maxframes = (int)(PREDICTIONTIME_JUMP / 0.1);
            cmdframes = 1;
            stopevent = SE_HITGROUND|SE_HITGROUNDDAMAGE|
                        SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA;
        } //end if
        else
        {
            maxframes = 2;
            cmdframes = 2;
            stopevent = SE_HITGROUNDDAMAGE|
                        SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA;
        } //end else
        //AAS_ClearShownDebugLines();
        //
        VectorCopy(ms->origin, origin);
        origin[2] += 0.5;
        AAS_PredictClientMovement(&move, ms->entitynum, origin, presencetype, qtrue,
                                  velocity, cmdmove, cmdframes, maxframes, 0.1f,
                                  stopevent, 0, qfalse);//qtrue);
        //if prediction time wasn't enough to fully predict the movement
        if (move.frames >= maxframes && (type & MOVE_JUMP))
        {
            //botimport.Print(PRT_MESSAGE, "client %d: max prediction frames\n", ms->client);
            return qfalse;
        } //end if
        //don't enter slime or lava and don't fall from too high
        if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE))
        {
            //botimport.Print(PRT_MESSAGE, "client %d: would be hurt ", ms->client);
            //if (move.stopevent & SE_ENTERSLIME) botimport.Print(PRT_MESSAGE, "slime\n");
            //if (move.stopevent & SE_ENTERLAVA) botimport.Print(PRT_MESSAGE, "lava\n");
            //if (move.stopevent & SE_HITGROUNDDAMAGE) botimport.Print(PRT_MESSAGE, "hitground\n");
            return qfalse;
        } //end if
        //if ground was hit
        if (move.stopevent & SE_HITGROUND)
        {
            //check for nearby gap
            VectorNormalize2(move.velocity, tmpdir);
            dist = BotGapDistance(move.endpos, tmpdir, ms->entitynum);
            if (dist > 0) return qfalse;
            //
            dist = BotGapDistance(move.endpos, hordir, ms->entitynum);
            if (dist > 0) return qfalse;
        } //end if
        //get horizontal movement
        tmpdir[0] = move.endpos[0] - ms->origin[0];
        tmpdir[1] = move.endpos[1] - ms->origin[1];
        tmpdir[2] = 0;
        //
        //AAS_DrawCross(move.endpos, 4, LINECOLOR_BLUE);
        //the bot is blocked by something
        if (VectorLength(tmpdir) < speed * ms->thinktime * 0.5) return qfalse;
        //perform the movement
        if (type & MOVE_JUMP) EA_Jump(ms->client);
        if (type & MOVE_CROUCH) EA_Crouch(ms->client);
        EA_Move(ms->client, hordir, speed);
        //movement was succesfull
        return qtrue;
    } //end if
    else
    {
        if (ms->moveflags & MFL_BARRIERJUMP)
        {
            //if near the top or going down
            if (ms->velocity[2] < 50)
            {
                EA_Move(ms->client, dir, speed);
            } //end if
        } //end if
        //FIXME: do air control to avoid hazards
        return qtrue;
    } //end else
} //end of the function BotWalkInDirection
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type)
{
    bot_movestate_t *ms;

    ms = BotMoveStateFromHandle(movestate);
    if (!ms) return qfalse;
    //if swimming
    if (AAS_Swimming(ms->origin))
    {
        return BotSwimInDirection(ms, dir, speed, type);
    } //end if
    else
    {
        return BotWalkInDirection(ms, dir, speed, type);
    } //end else
} //end of the function BotMoveInDirection
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int Intersection(vec2_t p1, vec2_t p2, vec2_t p3, vec2_t p4, vec2_t out)
{
    float x1, dx1, dy1, x2, dx2, dy2, d;

    dx1 = p2[0] - p1[0];
    dy1 = p2[1] - p1[1];
    dx2 = p4[0] - p3[0];
    dy2 = p4[1] - p3[1];

    d = dy1 * dx2 - dx1 * dy2;
    if (d != 0)
    {
        x1 = p1[1] * dx1 - p1[0] * dy1;
        x2 = p3[1] * dx2 - p3[0] * dy2;
        out[0] = (int) ((dx1 * x2 - dx2 * x1) / d);
        out[1] = (int) ((dy1 * x2 - dy2 * x1) / d);
        return qtrue;
    } //end if
    else
    {
        return qfalse;
    } //end else
} //end of the function Intersection
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotCheckBlocked(bot_movestate_t *ms, vec3_t dir, int checkbottom, bot_moveresult_t *result)
{
    vec3_t mins, maxs, end, up = {0, 0, 1};
    bsp_trace_t trace;

    //test for entities obstructing the bot's path
    AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs);
    //
    if (fabs(DotProduct(dir, up)) < 0.7)
    {
        mins[2] += sv_maxstep->value; //if the bot can step on
        maxs[2] -= 10; //a little lower to avoid low ceiling
    } //end if
    VectorMA(ms->origin, 3, dir, end);
    trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY);
    //if not started in solid and not hitting the world entity
    if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) )
    {
        result->blocked = qtrue;
        result->blockentity = trace.ent;
#ifdef DEBUG
        //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client);
#endif //DEBUG
    } //end if
    //if not in an area with reachability
    else if (checkbottom && !AAS_AreaReachability(ms->areanum))
    {
        //check if the bot is standing on something
        AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs);
        VectorMA(ms->origin, -3, up, end);
        trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
        if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) )
        {
            result->blocked = qtrue;
            result->blockentity = trace.ent;
            result->flags |= MOVERESULT_ONTOPOFOBSTACLE;
#ifdef DEBUG
            //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client);
#endif //DEBUG
        } //end if
    } //end else
} //end of the function BotCheckBlocked
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotClearMoveResult(bot_moveresult_t *moveresult)
{
    moveresult->failure = qfalse;
    moveresult->type = 0;
    moveresult->blocked = qfalse;
    moveresult->blockentity = 0;
    moveresult->traveltype = 0;
    moveresult->flags = 0;
} //end of the function BotClearMoveResult
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach)
{
    float dist, speed;
    vec3_t hordir;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //first walk straight to the reachability start
    hordir[0] = reach->start[0] - ms->origin[0];
    hordir[1] = reach->start[1] - ms->origin[1];
    hordir[2] = 0;
    dist = VectorNormalize(hordir);
    //
    BotCheckBlocked(ms, hordir, qtrue, &result);
    //
    if (dist < 10)
    {
        //walk straight to the reachability end
        hordir[0] = reach->end[0] - ms->origin[0];
        hordir[1] = reach->end[1] - ms->origin[1];
        hordir[2] = 0;
        dist = VectorNormalize(hordir);
    } //end if
    //if going towards a crouch area
    if (!(AAS_AreaPresenceType(reach->areanum) & PRESENCE_NORMAL))
    {
        //if pretty close to the reachable area
        if (dist < 20) EA_Crouch(ms->client);
    } //end if
    //
    dist = BotGapDistance(ms->origin, hordir, ms->entitynum);
    //
    if (ms->moveflags & MFL_WALK)
    {
        if (dist > 0) speed = 200 - (180 - 1 * dist);
        else speed = 200;
        EA_Walk(ms->client);
    } //end if
    else
    {
        if (dist > 0) speed = 400 - (360 - 2 * dist);
        else speed = 400;
    } //end else
    //elemantary action move in direction
    EA_Move(ms->client, hordir, speed);
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotTravel_Walk
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t hordir;
    float dist, speed;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //if not on the ground and changed areas... don't walk back!!
    //(doesn't seem to help)
    /*
    ms->areanum = BotFuzzyPointReachabilityArea(ms->origin);
    if (ms->areanum == reach->areanum)
    {
    #ifdef DEBUG
    	botimport.Print(PRT_MESSAGE, "BotFinishTravel_Walk: already in reach area\n");
    #endif //DEBUG
    	return result;
    } //end if*/
    //go straight to the reachability end
    hordir[0] = reach->end[0] - ms->origin[0];
    hordir[1] = reach->end[1] - ms->origin[1];
    hordir[2] = 0;
    dist = VectorNormalize(hordir);
    //
    if (dist > 100) dist = 100;
    speed = 400 - (400 - 3 * dist);
    //
    EA_Move(ms->client, hordir, speed);
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotFinishTravel_Walk
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Crouch(bot_movestate_t *ms, aas_reachability_t *reach)
{
    float speed;
    vec3_t hordir;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //
    speed = 400;
    //walk straight to reachability end
    hordir[0] = reach->end[0] - ms->origin[0];
    hordir[1] = reach->end[1] - ms->origin[1];
    hordir[2] = 0;
    VectorNormalize(hordir);
    //
    BotCheckBlocked(ms, hordir, qtrue, &result);
    //elemantary actions
    EA_Crouch(ms->client);
    EA_Move(ms->client, hordir, speed);
    //
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotTravel_Crouch
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach)
{
    float dist, speed;
    vec3_t hordir;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //walk straight to reachability start
    hordir[0] = reach->start[0] - ms->origin[0];
    hordir[1] = reach->start[1] - ms->origin[1];
    hordir[2] = 0;
    dist = VectorNormalize(hordir);
    //
    BotCheckBlocked(ms, hordir, qtrue, &result);
    //if pretty close to the barrier
    if (dist < 9)
    {
        EA_Jump(ms->client);
    } //end if
    else
    {
        if (dist > 60) dist = 60;
        speed = 360 - (360 - 6 * dist);
        EA_Move(ms->client, hordir, speed);
    } //end else
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotTravel_BarrierJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach)
{
    float dist;
    vec3_t hordir;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //if near the top or going down
    if (ms->velocity[2] < 250)
    {
        hordir[0] = reach->end[0] - ms->origin[0];
        hordir[1] = reach->end[1] - ms->origin[1];
        hordir[2] = 0;
        dist = VectorNormalize(hordir);
        //
        BotCheckBlocked(ms, hordir, qtrue, &result);
        //
        EA_Move(ms->client, hordir, 400);
        VectorCopy(hordir, result.movedir);
    } //end if
    //
    return result;
} //end of the function BotFinishTravel_BarrierJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Swim(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t dir;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //swim straight to reachability end
    VectorSubtract(reach->start, ms->origin, dir);
    VectorNormalize(dir);
    //
    BotCheckBlocked(ms, dir, qtrue, &result);
    //elemantary actions
    EA_Move(ms->client, dir, 400);
    //
    VectorCopy(dir, result.movedir);
    Vector2Angles(dir, result.ideal_viewangles);
    result.flags |= MOVERESULT_SWIMVIEW;
    //
    return result;
} //end of the function BotTravel_Swim
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t dir, hordir;
    float dist;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //swim straight to reachability end
    VectorSubtract(reach->end, ms->origin, dir);
    VectorCopy(dir, hordir);
    hordir[2] = 0;
    dir[2] += 15 + crandom() * 40;
    //botimport.Print(PRT_MESSAGE, "BotTravel_WaterJump: dir[2] = %f\n", dir[2]);
    VectorNormalize(dir);
    dist = VectorNormalize(hordir);
    //elemantary actions
    //EA_Move(ms->client, dir, 400);
    EA_MoveForward(ms->client);
    //move up if close to the actual out of water jump spot
    if (dist < 40) EA_MoveUp(ms->client);
    //set the ideal view angles
    Vector2Angles(dir, result.ideal_viewangles);
    result.flags |= MOVERESULT_MOVEMENTVIEW;
    //
    VectorCopy(dir, result.movedir);
    //
    return result;
} //end of the function BotTravel_WaterJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t dir, pnt;
    float dist;
    bot_moveresult_t result;

    //botimport.Print(PRT_MESSAGE, "BotFinishTravel_WaterJump\n");
    BotClearMoveResult(&result);
    //if waterjumping there's nothing to do
    if (ms->moveflags & MFL_WATERJUMP) return result;
    //if not touching any water anymore don't do anything
    //otherwise the bot sometimes keeps jumping?
    VectorCopy(ms->origin, pnt);
    pnt[2] -= 32;	//extra for q2dm4 near red armor/mega health
    if (!(AAS_PointContents(pnt) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) return result;
    //swim straight to reachability end
    VectorSubtract(reach->end, ms->origin, dir);
    dir[0] += crandom() * 10;
    dir[1] += crandom() * 10;
    dir[2] += 70 + crandom() * 10;
    dist = VectorNormalize(dir);
    //elemantary actions
    EA_Move(ms->client, dir, 400);
    //set the ideal view angles
    Vector2Angles(dir, result.ideal_viewangles);
    result.flags |= MOVERESULT_MOVEMENTVIEW;
    //
    VectorCopy(dir, result.movedir);
    //
    return result;
} //end of the function BotFinishTravel_WaterJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t hordir, dir;
    float dist, speed, reachhordist;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //check if the bot is blocked by anything
    VectorSubtract(reach->start, ms->origin, dir);
    VectorNormalize(dir);
    BotCheckBlocked(ms, dir, qtrue, &result);
    //if the reachability start and end are practially above each other
    VectorSubtract(reach->end, reach->start, dir);
    dir[2] = 0;
    reachhordist = VectorLength(dir);
    //walk straight to the reachability start
    hordir[0] = reach->start[0] - ms->origin[0];
    hordir[1] = reach->start[1] - ms->origin[1];
    hordir[2] = 0;
    dist = VectorNormalize(hordir);
    //if pretty close to the start focus on the reachability end
    if (dist < 48)
    {
        hordir[0] = reach->end[0] - ms->origin[0];
        hordir[1] = reach->end[1] - ms->origin[1];
        hordir[2] = 0;
        VectorNormalize(hordir);
        //
        if (reachhordist < 20)
        {
            speed = 100;
        } //end if
        else if (!AAS_HorizontalVelocityForJump(0, reach->start, reach->end, &speed))
        {
            speed = 400;
        } //end if
    } //end if
    else
    {
        if (reachhordist < 20)
        {
            if (dist > 64) dist = 64;
            speed = 400 - (256 - 4 * dist);
        } //end if
        else
        {
            speed = 400;
        } //end else
    } //end else
    //
    BotCheckBlocked(ms, hordir, qtrue, &result);
    //elemantary action
    EA_Move(ms->client, hordir, speed);
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotTravel_WalkOffLedge
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotAirControl(vec3_t origin, vec3_t velocity, vec3_t goal, vec3_t dir, float *speed)
{
    vec3_t org, vel;
    float dist;
    int i;

    VectorCopy(origin, org);
    VectorScale(velocity, 0.1, vel);
    for (i = 0; i < 50; i++)
    {
        vel[2] -= sv_gravity->value * 0.01;
        //if going down and next position would be below the goal
        if (vel[2] < 0 && org[2] + vel[2] < goal[2])
        {
            VectorScale(vel, (goal[2] - org[2]) / vel[2], vel);
            VectorAdd(org, vel, org);
            VectorSubtract(goal, org, dir);
            dist = VectorNormalize(dir);
            if (dist > 32) dist = 32;
            *speed = 400 - (400 - 13 * dist);
            return qtrue;
        } //end if
        else
        {
            VectorAdd(org, vel, org);
        } //end else
    } //end for
    VectorSet(dir, 0, 0, 0);
    *speed = 400;
    return qfalse;
} //end of the function BotAirControl
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t dir, hordir, end, v;
    float dist, speed;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //
    VectorSubtract(reach->end, ms->origin, dir);
    BotCheckBlocked(ms, dir, qtrue, &result);
    //
    VectorSubtract(reach->end, ms->origin, v);
    v[2] = 0;
    dist = VectorNormalize(v);
    if (dist > 16) VectorMA(reach->end, 16, v, end);
    else VectorCopy(reach->end, end);
    //
    if (!BotAirControl(ms->origin, ms->velocity, end, hordir, &speed))
    {
        //go straight to the reachability end
        VectorCopy(dir, hordir);
        hordir[2] = 0;
        //
        dist = VectorNormalize(hordir);
        speed = 400;
    } //end if
    //
    EA_Move(ms->client, hordir, speed);
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotFinishTravel_WalkOffLedge
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
/*
bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach)
{
	vec3_t hordir;
	float dist, gapdist, speed, horspeed, sv_jumpvel;
	bot_moveresult_t result;

	BotClearMoveResult(&result);
	//
	sv_jumpvel = botlibglobals.sv_jumpvel->value;
	//walk straight to the reachability start
	hordir[0] = reach->start[0] - ms->origin[0];
	hordir[1] = reach->start[1] - ms->origin[1];
	hordir[2] = 0;
	dist = VectorNormalize(hordir);
	//
	speed = 350;
	//
	gapdist = BotGapDistance(ms, hordir, ms->entitynum);
	//if pretty close to the start focus on the reachability end
	if (dist < 50 || (gapdist && gapdist < 50))
	{
		//NOTE: using max speed (400) works best
		//if (AAS_HorizontalVelocityForJump(sv_jumpvel, ms->origin, reach->end, &horspeed))
		//{
		//	speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value;
		//} //end if
		hordir[0] = reach->end[0] - ms->origin[0];
		hordir[1] = reach->end[1] - ms->origin[1];
		VectorNormalize(hordir);
		//elemantary action jump
		EA_Jump(ms->client);
		//
		ms->jumpreach = ms->lastreachnum;
		speed = 600;
	} //end if
	else
	{
		if (AAS_HorizontalVelocityForJump(sv_jumpvel, reach->start, reach->end, &horspeed))
		{
			speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value;
		} //end if
	} //end else
	//elemantary action
	EA_Move(ms->client, hordir, speed);
	VectorCopy(hordir, result.movedir);
	//
	return result;
} //end of the function BotTravel_Jump*/
/*
bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach)
{
	vec3_t hordir, dir1, dir2, mins, maxs, start, end;
	float dist1, dist2, speed;
	bot_moveresult_t result;
	bsp_trace_t trace;

	BotClearMoveResult(&result);
	//
	hordir[0] = reach->start[0] - reach->end[0];
	hordir[1] = reach->start[1] - reach->end[1];
	hordir[2] = 0;
	VectorNormalize(hordir);
	//
	VectorCopy(reach->start, start);
	start[2] += 1;
	//minus back the bouding box size plus 16
	VectorMA(reach->start, 80, hordir, end);
	//
	AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, mins, maxs);
	//check for solids
	trace = AAS_Trace(start, mins, maxs, end, ms->entitynum, MASK_PLAYERSOLID);
	if (trace.startsolid) VectorCopy(start, trace.endpos);
	//check for a gap
	for (dist1 = 0; dist1 < 80; dist1 += 10)
	{
		VectorMA(start, dist1+10, hordir, end);
		end[2] += 1;
		if (AAS_PointAreaNum(end) != ms->reachareanum) break;
	} //end for
	if (dist1 < 80) VectorMA(reach->start, dist1, hordir, trace.endpos);
//	dist1 = BotGapDistance(start, hordir, ms->entitynum);
//	if (dist1 && dist1 <= trace.fraction * 80) VectorMA(reach->start, dist1-20, hordir, trace.endpos);
	//
	VectorSubtract(ms->origin, reach->start, dir1);
	dir1[2] = 0;
	dist1 = VectorNormalize(dir1);
	VectorSubtract(ms->origin, trace.endpos, dir2);
	dir2[2] = 0;
	dist2 = VectorNormalize(dir2);
	//if just before the reachability start
	if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5)
	{
		//botimport.Print(PRT_MESSAGE, "between jump start and run to point\n");
		hordir[0] = reach->end[0] - ms->origin[0];
		hordir[1] = reach->end[1] - ms->origin[1];
		hordir[2] = 0;
		VectorNormalize(hordir);
		//elemantary action jump
		if (dist1 < 24) EA_Jump(ms->client);
		else if (dist1 < 32) EA_DelayedJump(ms->client);
		EA_Move(ms->client, hordir, 600);
		//
		ms->jumpreach = ms->lastreachnum;
	} //end if
	else
	{
		//botimport.Print(PRT_MESSAGE, "going towards run to point\n");
		hordir[0] = trace.endpos[0] - ms->origin[0];
		hordir[1] = trace.endpos[1] - ms->origin[1];
		hordir[2] = 0;
		VectorNormalize(hordir);
		//
		if (dist2 > 80) dist2 = 80;
		speed = 400 - (400 - 5 * dist2);
		EA_Move(ms->client, hordir, speed);
	} //end else
	VectorCopy(hordir, result.movedir);
	//
	return result;
} //end of the function BotTravel_Jump*/
//*
bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t hordir, dir1, dir2, start, end, runstart;
    //	vec3_t runstart, dir1, dir2, hordir;
    float dist1, dist2, speed;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //
    AAS_JumpReachRunStart(reach, runstart);
    //*
    hordir[0] = runstart[0] - reach->start[0];
    hordir[1] = runstart[1] - reach->start[1];
    hordir[2] = 0;
    VectorNormalize(hordir);
    //
    VectorCopy(reach->start, start);
    start[2] += 1;
    VectorMA(reach->start, 80, hordir, runstart);
    //check for a gap
    for (dist1 = 0; dist1 < 80; dist1 += 10)
    {
        VectorMA(start, dist1+10, hordir, end);
        end[2] += 1;
        if (AAS_PointAreaNum(end) != ms->reachareanum) break;
    } //end for
    if (dist1 < 80) VectorMA(reach->start, dist1, hordir, runstart);
    //
    VectorSubtract(ms->origin, reach->start, dir1);
    dir1[2] = 0;
    dist1 = VectorNormalize(dir1);
    VectorSubtract(ms->origin, runstart, dir2);
    dir2[2] = 0;
    dist2 = VectorNormalize(dir2);
    //if just before the reachability start
    if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5)
    {
        //		botimport.Print(PRT_MESSAGE, "between jump start and run start point\n");
        hordir[0] = reach->end[0] - ms->origin[0];
        hordir[1] = reach->end[1] - ms->origin[1];
        hordir[2] = 0;
        VectorNormalize(hordir);
        //elemantary action jump
        if (dist1 < 24) EA_Jump(ms->client);
        else if (dist1 < 32) EA_DelayedJump(ms->client);
        EA_Move(ms->client, hordir, 600);
        //
        ms->jumpreach = ms->lastreachnum;
    } //end if
    else
    {
        //		botimport.Print(PRT_MESSAGE, "going towards run start point\n");
        hordir[0] = runstart[0] - ms->origin[0];
        hordir[1] = runstart[1] - ms->origin[1];
        hordir[2] = 0;
        VectorNormalize(hordir);
        //
        if (dist2 > 80) dist2 = 80;
        speed = 400 - (400 - 5 * dist2);
        EA_Move(ms->client, hordir, speed);
    } //end else
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotTravel_Jump*/
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t hordir, hordir2;
    float speed, dist;
    bot_moveresult_t result;

    VectorClear(result.movedir);

    BotClearMoveResult(&result);
    //if not jumped yet
    if (!ms->jumpreach) return result;
    //go straight to the reachability end
    hordir[0] = reach->end[0] - ms->origin[0];
    hordir[1] = reach->end[1] - ms->origin[1];
    hordir[2] = 0;
    dist = VectorNormalize(hordir);
    //
    hordir2[0] = reach->end[0] - reach->start[0];
    hordir2[1] = reach->end[1] - reach->start[1];
    hordir2[2] = 0;
    VectorNormalize(hordir2);
    //
    if (DotProduct(hordir, hordir2) < -0.5 && dist < 24) return result;
    //always use max speed when traveling through the air
    speed = 800;
    //
    EA_Move(ms->client, hordir, speed);
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotFinishTravel_Jump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Ladder(bot_movestate_t *ms, aas_reachability_t *reach)
{
    //float dist, speed;
    vec3_t dir, viewdir;//, hordir;
    vec3_t origin = {0, 0, 0};
    //	vec3_t up = {0, 0, 1};
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //
    //	if ((ms->moveflags & MFL_AGAINSTLADDER))
    //NOTE: not a good idea for ladders starting in water
    // || !(ms->moveflags & MFL_ONGROUND))
    {
        //botimport.Print(PRT_MESSAGE, "against ladder or not on ground\n");
        VectorSubtract(reach->end, ms->origin, dir);
        VectorNormalize(dir);
        //set the ideal view angles, facing the ladder up or down
        viewdir[0] = dir[0];
        viewdir[1] = dir[1];
        viewdir[2] = 3 * dir[2];
        Vector2Angles(viewdir, result.ideal_viewangles);
        //elemantary action
        EA_Move(ms->client, origin, 0);
        EA_MoveForward(ms->client);
        //set movement view flag so the AI can see the view is focussed
        result.flags |= MOVERESULT_MOVEMENTVIEW;
    } //end if
    /*	else
    	{
    		//botimport.Print(PRT_MESSAGE, "moving towards ladder\n");
    		VectorSubtract(reach->end, ms->origin, dir);
    		//make sure the horizontal movement is large anough
    		VectorCopy(dir, hordir);
    		hordir[2] = 0;
    		dist = VectorNormalize(hordir);
    		//
    		dir[0] = hordir[0];
    		dir[1] = hordir[1];
    		if (dir[2] > 0) dir[2] = 1;
    		else dir[2] = -1;
    		if (dist > 50) dist = 50;
    		speed = 400 - (200 - 4 * dist);
    		EA_Move(ms->client, dir, speed);
    	} //end else*/
    //save the movement direction
    VectorCopy(dir, result.movedir);
    //
    return result;
} //end of the function BotTravel_Ladder
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Teleport(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t hordir;
    float dist;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //if the bot is being teleported
    if (ms->moveflags & MFL_TELEPORTED) return result;

    //walk straight to center of the teleporter
    VectorSubtract(reach->start, ms->origin, hordir);
    if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0;
    dist = VectorNormalize(hordir);
    //
    BotCheckBlocked(ms, hordir, qtrue, &result);

    if (dist < 30) EA_Move(ms->client, hordir, 200);
    else EA_Move(ms->client, hordir, 400);

    if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW;

    VectorCopy(hordir, result.movedir);
    return result;
} //end of the function BotTravel_Teleport
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t dir, dir1, dir2, hordir, bottomcenter;
    float dist, dist1, dist2, speed;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //if standing on the plat
    if (BotOnMover(ms->origin, ms->entitynum, reach))
    {
#ifdef DEBUG_ELEVATOR
        botimport.Print(PRT_MESSAGE, "bot on elevator\n");
#endif //DEBUG_ELEVATOR
        //if vertically not too far from the end point
        if (fabs(ms->origin[2] - reach->end[2]) < sv_maxbarrier->value)
        {
#ifdef DEBUG_ELEVATOR
            botimport.Print(PRT_MESSAGE, "bot moving to end\n");
#endif //DEBUG_ELEVATOR
            //move to the end point
            VectorSubtract(reach->end, ms->origin, hordir);
            hordir[2] = 0;
            VectorNormalize(hordir);
            if (!BotCheckBarrierJump(ms, hordir, 100))
            {
                EA_Move(ms->client, hordir, 400);
            } //end if
            VectorCopy(hordir, result.movedir);
        } //end else
        //if not really close to the center of the elevator
        else
        {
            MoverBottomCenter(reach, bottomcenter);
            VectorSubtract(bottomcenter, ms->origin, hordir);
            hordir[2] = 0;
            dist = VectorNormalize(hordir);
            //
            if (dist > 10)
            {
#ifdef DEBUG_ELEVATOR
                botimport.Print(PRT_MESSAGE, "bot moving to center\n");
#endif //DEBUG_ELEVATOR
                //move to the center of the plat
                if (dist > 100) dist = 100;
                speed = 400 - (400 - 4 * dist);
                //
                EA_Move(ms->client, hordir, speed);
                VectorCopy(hordir, result.movedir);
            } //end if
        } //end else
    } //end if
    else
    {
#ifdef DEBUG_ELEVATOR
        botimport.Print(PRT_MESSAGE, "bot not on elevator\n");
#endif //DEBUG_ELEVATOR
        //if very near the reachability end
        VectorSubtract(reach->end, ms->origin, dir);
        dist = VectorLength(dir);
        if (dist < 64)
        {
            if (dist > 60) dist = 60;
            speed = 360 - (360 - 6 * dist);
            //
            if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50))
            {
                if (speed > 5) EA_Move(ms->client, dir, speed);
            } //end if
            VectorCopy(dir, result.movedir);
            //
            if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW;
            //stop using this reachability
            ms->reachability_time = 0;
            return result;
        } //end if
        //get direction and distance to reachability start
        VectorSubtract(reach->start, ms->origin, dir1);
        if (!(ms->moveflags & MFL_SWIMMING)) dir1[2] = 0;
        dist1 = VectorNormalize(dir1);
        //if the elevator isn't down
        if (!MoverDown(reach))
        {
#ifdef DEBUG_ELEVATOR
            botimport.Print(PRT_MESSAGE, "elevator not down\n");
#endif //DEBUG_ELEVATOR
            dist = dist1;
            VectorCopy(dir1, dir);
            //
            BotCheckBlocked(ms, dir, qfalse, &result);
            //
            if (dist > 60) dist = 60;
            speed = 360 - (360 - 6 * dist);
            //
            if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50))
            {
                if (speed > 5) EA_Move(ms->client, dir, speed);
            } //end if
            VectorCopy(dir, result.movedir);
            //
            if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW;
            //this isn't a failure... just wait till the elevator comes down
            result.type = RESULTTYPE_ELEVATORUP;
            result.flags |= MOVERESULT_WAITING;
            return result;
        } //end if
        //get direction and distance to elevator bottom center
        MoverBottomCenter(reach, bottomcenter);
        VectorSubtract(bottomcenter, ms->origin, dir2);
        if (!(ms->moveflags & MFL_SWIMMING)) dir2[2] = 0;
        dist2 = VectorNormalize(dir2);
        //if very close to the reachability start or
        //closer to the elevator center or
        //between reachability start and elevator center
        if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0)
        {
#ifdef DEBUG_ELEVATOR
            botimport.Print(PRT_MESSAGE, "bot moving to center\n");
#endif //DEBUG_ELEVATOR
            dist = dist2;
            VectorCopy(dir2, dir);
        } //end if
        else //closer to the reachability start
        {
#ifdef DEBUG_ELEVATOR
            botimport.Print(PRT_MESSAGE, "bot moving to start\n");
#endif //DEBUG_ELEVATOR
            dist = dist1;
            VectorCopy(dir1, dir);
        } //end else
        //
        BotCheckBlocked(ms, dir, qfalse, &result);
        //
        if (dist > 60) dist = 60;
        speed = 400 - (400 - 6 * dist);
        //
        if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50))
        {
            EA_Move(ms->client, dir, speed);
        } //end if
        VectorCopy(dir, result.movedir);
        //
        if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW;
    } //end else
    return result;
} //end of the function BotTravel_Elevator
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t bottomcenter, bottomdir, topdir;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //
    MoverBottomCenter(reach, bottomcenter);
    VectorSubtract(bottomcenter, ms->origin, bottomdir);
    //
    VectorSubtract(reach->end, ms->origin, topdir);
    //
    if (fabs(bottomdir[2]) < fabs(topdir[2]))
    {
        VectorNormalize(bottomdir);
        EA_Move(ms->client, bottomdir, 300);
    } //end if
    else
    {
        VectorNormalize(topdir);
        EA_Move(ms->client, topdir, 300);
    } //end else
    return result;
} //end of the function BotFinishTravel_Elevator
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotFuncBobStartEnd(aas_reachability_t *reach, vec3_t start, vec3_t end, vec3_t origin)
{
    int spawnflags, modelnum;
    vec3_t mins, maxs, mid, angles = {0, 0, 0};
    int num0, num1;

    modelnum = reach->facenum & 0x0000FFFF;
    if (!AAS_OriginOfMoverWithModelNum(modelnum, origin))
    {
        botimport.Print(PRT_MESSAGE, "BotFuncBobStartEnd: no entity with model %d\n", modelnum);
        VectorSet(start, 0, 0, 0);
        VectorSet(end, 0, 0, 0);
        return;
    } //end if
    AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL);
    VectorAdd(mins, maxs, mid);
    VectorScale(mid, 0.5, mid);
    VectorCopy(mid, start);
    VectorCopy(mid, end);
    spawnflags = reach->facenum >> 16;
    num0 = reach->edgenum >> 16;
    if (num0 > 0x00007FFF) num0 |= 0xFFFF0000;
    num1 = reach->edgenum & 0x0000FFFF;
    if (num1 > 0x00007FFF) num1 |= 0xFFFF0000;
    if (spawnflags & 1)
    {
        start[0] = num0;
        end[0] = num1;
        //
        origin[0] += mid[0];
        origin[1] = mid[1];
        origin[2] = mid[2];
    } //end if
    else if (spawnflags & 2)
    {
        start[1] = num0;
        end[1] = num1;
        //
        origin[0] = mid[0];
        origin[1] += mid[1];
        origin[2] = mid[2];
    } //end else if
    else
    {
        start[2] = num0;
        end[2] = num1;
        //
        origin[0] = mid[0];
        origin[1] = mid[1];
        origin[2] += mid[2];
    } //end else
} //end of the function BotFuncBobStartEnd
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t dir, dir1, dir2, hordir, bottomcenter, bob_start, bob_end, bob_origin;
    float dist, dist1, dist2, speed;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //
    BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin);
    //if standing ontop of the func_bobbing
    if (BotOnMover(ms->origin, ms->entitynum, reach))
    {
#ifdef DEBUG_FUNCBOB
        botimport.Print(PRT_MESSAGE, "bot on func_bobbing\n");
#endif
        //if near end point of reachability
        VectorSubtract(bob_origin, bob_end, dir);
        if (VectorLength(dir) < 24)
        {
#ifdef DEBUG_FUNCBOB
            botimport.Print(PRT_MESSAGE, "bot moving to reachability end\n");
#endif
            //move to the end point
            VectorSubtract(reach->end, ms->origin, hordir);
            hordir[2] = 0;
            VectorNormalize(hordir);
            if (!BotCheckBarrierJump(ms, hordir, 100))
            {
                EA_Move(ms->client, hordir, 400);
            } //end if
            VectorCopy(hordir, result.movedir);
        } //end else
        //if not really close to the center of the elevator
        else
        {
            MoverBottomCenter(reach, bottomcenter);
            VectorSubtract(bottomcenter, ms->origin, hordir);
            hordir[2] = 0;
            dist = VectorNormalize(hordir);
            //
            if (dist > 10)
            {
#ifdef DEBUG_FUNCBOB
                botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n");
#endif
                //move to the center of the plat
                if (dist > 100) dist = 100;
                speed = 400 - (400 - 4 * dist);
                //
                EA_Move(ms->client, hordir, speed);
                VectorCopy(hordir, result.movedir);
            } //end if
        } //end else
    } //end if
    else
    {
#ifdef DEBUG_FUNCBOB
        botimport.Print(PRT_MESSAGE, "bot not ontop of func_bobbing\n");
#endif
        //if very near the reachability end
        VectorSubtract(reach->end, ms->origin, dir);
        dist = VectorLength(dir);
        if (dist < 64)
        {
#ifdef DEBUG_FUNCBOB
            botimport.Print(PRT_MESSAGE, "bot moving to end\n");
#endif
            if (dist > 60) dist = 60;
            speed = 360 - (360 - 6 * dist);
            //if swimming or no barrier jump
            if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50))
            {
                if (speed > 5) EA_Move(ms->client, dir, speed);
            } //end if
            VectorCopy(dir, result.movedir);
            //
            if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW;
            //stop using this reachability
            ms->reachability_time = 0;
            return result;
        } //end if
        //get direction and distance to reachability start
        VectorSubtract(reach->start, ms->origin, dir1);
        if (!(ms->moveflags & MFL_SWIMMING)) dir1[2] = 0;
        dist1 = VectorNormalize(dir1);
        //if func_bobbing is Not it's start position
        VectorSubtract(bob_origin, bob_start, dir);
        if (VectorLength(dir) > 16)
        {
#ifdef DEBUG_FUNCBOB
            botimport.Print(PRT_MESSAGE, "func_bobbing not at start\n");
#endif
            dist = dist1;
            VectorCopy(dir1, dir);
            //
            BotCheckBlocked(ms, dir, qfalse, &result);
            //
            if (dist > 60) dist = 60;
            speed = 360 - (360 - 6 * dist);
            //
            if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50))
            {
                if (speed > 5) EA_Move(ms->client, dir, speed);
            } //end if
            VectorCopy(dir, result.movedir);
            //
            if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW;
            //this isn't a failure... just wait till the func_bobbing arrives
            result.type = RESULTTYPE_WAITFORFUNCBOBBING;
            result.flags |= MOVERESULT_WAITING;
            return result;
        } //end if
        //get direction and distance to func_bob bottom center
        MoverBottomCenter(reach, bottomcenter);
        VectorSubtract(bottomcenter, ms->origin, dir2);
        if (!(ms->moveflags & MFL_SWIMMING)) dir2[2] = 0;
        dist2 = VectorNormalize(dir2);
        //if very close to the reachability start or
        //closer to the elevator center or
        //between reachability start and func_bobbing center
        if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0)
        {
#ifdef DEBUG_FUNCBOB
            botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n");
#endif
            dist = dist2;
            VectorCopy(dir2, dir);
        } //end if
        else //closer to the reachability start
        {
#ifdef DEBUG_FUNCBOB
            botimport.Print(PRT_MESSAGE, "bot moving to reachability start\n");
#endif
            dist = dist1;
            VectorCopy(dir1, dir);
        } //end else
        //
        BotCheckBlocked(ms, dir, qfalse, &result);
        //
        if (dist > 60) dist = 60;
        speed = 400 - (400 - 6 * dist);
        //
        if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50))
        {
            EA_Move(ms->client, dir, speed);
        } //end if
        VectorCopy(dir, result.movedir);
        //
        if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW;
    } //end else
    return result;
} //end of the function BotTravel_FuncBobbing
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t bob_origin, bob_start, bob_end, dir, hordir, bottomcenter;
    bot_moveresult_t result;
    float dist, speed;

    VectorClear(result.movedir);
    BotClearMoveResult(&result);
    //
    BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin);
    //
    VectorSubtract(bob_origin, bob_end, dir);
    dist = VectorLength(dir);
    //if the func_bobbing is near the end
    if (dist < 16)
    {
        VectorSubtract(reach->end, ms->origin, hordir);
        if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0;
        dist = VectorNormalize(hordir);
        //
        if (dist > 60) dist = 60;
        speed = 360 - (360 - 6 * dist);
        //
        if (speed > 5) EA_Move(ms->client, dir, speed);
        VectorCopy(dir, result.movedir);
        //
        if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW;
    } //end if
    else
    {
        MoverBottomCenter(reach, bottomcenter);
        VectorSubtract(bottomcenter, ms->origin, hordir);
        if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0;
        dist = VectorNormalize(hordir);
        //
        if (dist > 5)
        {
            //move to the center of the plat
            if (dist > 100) dist = 100;
            speed = 400 - (400 - 4 * dist);
            //
            EA_Move(ms->client, hordir, speed);
            VectorCopy(hordir, result.movedir);
        } //end if
    } //end else
    return result;
} //end of the function BotFinishTravel_FuncBobbing
//===========================================================================
// 0  no valid grapple hook visible
// 1  the grapple hook is still flying
// 2  the grapple hooked into a wall
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int GrappleState(bot_movestate_t *ms, aas_reachability_t *reach)
{
    int i;
    aas_entityinfo_t entinfo;

    //if the grapple hook is pulling
    if (ms->moveflags & MFL_GRAPPLEPULL)
        return 2;
    //check for a visible grapple missile entity
    //or visible grapple entity
    for (i = AAS_NextEntity(0); i; i = AAS_NextEntity(i))
    {
        if (AAS_EntityType(i) == (int) entitytypemissile->value)
        {
            AAS_EntityInfo(i, &entinfo);
            if (entinfo.weapon1 == (int)weapindex_grapple->value || entinfo.weapon2 == (int)weapindex_grapple->value)
            {
                return 1;
            } //end if
        } //end if
    } //end for
    //no valid grapple at all
    return 0;
} //end of the function GrappleState
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotResetGrapple(bot_movestate_t *ms)
{
    aas_reachability_t reach;

    AAS_ReachabilityFromNum(ms->lastreachnum, &reach);
    //if not using the grapple hook reachability anymore
    if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_GRAPPLEHOOK)
    {
        if ((ms->moveflags & MFL_ACTIVEGRAPPLE) || ms->grapplevisible_time)
        {
            if (offhandgrapple->value)
                EA_Command(ms->client, cmd_grappleoff->string);
            ms->moveflags &= ~MFL_ACTIVEGRAPPLE;
            ms->grapplevisible_time = 0;
#ifdef DEBUG_GRAPPLE
            botimport.Print(PRT_MESSAGE, "reset grapple\n");
#endif //DEBUG_GRAPPLE
        } //end if
    } //end if
} //end of the function BotResetGrapple
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Grapple(bot_movestate_t *ms, aas_reachability_t *reach)
{
    bot_moveresult_t result;
    float dist, speed;
    vec3_t dir, viewdir, org;
    int state, areanum;
    bsp_trace_t trace;

#ifdef DEBUG_GRAPPLE
    static int debugline;
    if (!debugline) debugline = botimport.DebugLineCreate();
    botimport.DebugLineShow(debugline, reach->start, reach->end, LINECOLOR_BLUE);
#endif //DEBUG_GRAPPLE

    BotClearMoveResult(&result);
    //
    if (ms->moveflags & MFL_GRAPPLERESET)
    {
        if (offhandgrapple->value)
            EA_Command(ms->client, cmd_grappleoff->string);
        ms->moveflags &= ~MFL_ACTIVEGRAPPLE;
        return result;
    } //end if
    //
    if (!(int) offhandgrapple->value)
    {
        result.weapon = (int)weapindex_grapple->value;
        result.flags |= MOVERESULT_MOVEMENTWEAPON;
    } //end if
    //
    if (ms->moveflags & MFL_ACTIVEGRAPPLE)
    {
#ifdef DEBUG_GRAPPLE
        botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: active grapple\n");
#endif //DEBUG_GRAPPLE
        //
        state = GrappleState(ms, reach);
        //
        VectorSubtract(reach->end, ms->origin, dir);
        dir[2] = 0;
        dist = VectorLength(dir);
        //if very close to the grapple end or the grappled is hooked and
        //the bot doesn't get any closer
        if (state && dist < 48)
        {
            if (ms->lastgrappledist - dist < 1)
            {
#ifdef DEBUG_GRAPPLE
                botimport.Print(PRT_ERROR, "grapple normal end\n");
#endif //DEBUG_GRAPPLE
                if (offhandgrapple->value)
                    EA_Command(ms->client, cmd_grappleoff->string);
                ms->moveflags &= ~MFL_ACTIVEGRAPPLE;
                ms->moveflags |= MFL_GRAPPLERESET;
                ms->reachability_time = 0;	//end the reachability
                return result;
            } //end if
        } //end if
        //if no valid grapple at all, or the grapple hooked and the bot
        //isn't moving anymore
        else if (!state || (state == 2 && dist > ms->lastgrappledist - 2))
        {
            if (ms->grapplevisible_time < AAS_Time() - 0.4)
            {
#ifdef DEBUG_GRAPPLE
                botimport.Print(PRT_ERROR, "grapple not visible\n");
#endif //DEBUG_GRAPPLE
                if (offhandgrapple->value)
                    EA_Command(ms->client, cmd_grappleoff->string);
                ms->moveflags &= ~MFL_ACTIVEGRAPPLE;
                ms->moveflags |= MFL_GRAPPLERESET;
                ms->reachability_time = 0;	//end the reachability
                return result;
            } //end if
        } //end if
        else
        {
            ms->grapplevisible_time = AAS_Time();
        } //end else
        //
        if (!(int) offhandgrapple->value)
        {
            EA_Attack(ms->client);
        } //end if
        //remember the current grapple distance
        ms->lastgrappledist = dist;
    } //end if
    else
    {
#ifdef DEBUG_GRAPPLE
        botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: inactive grapple\n");
#endif //DEBUG_GRAPPLE
        //
        ms->grapplevisible_time = AAS_Time();
        //
        VectorSubtract(reach->start, ms->origin, dir);
        if (!(ms->moveflags & MFL_SWIMMING)) dir[2] = 0;
        VectorAdd(ms->origin, ms->viewoffset, org);
        VectorSubtract(reach->end, org, viewdir);
        //
        dist = VectorNormalize(dir);
        Vector2Angles(viewdir, result.ideal_viewangles);
        result.flags |= MOVERESULT_MOVEMENTVIEW;
        //
        if (dist < 5 &&
                fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 2 &&
                fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 2)
        {
#ifdef DEBUG_GRAPPLE
            botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: activating grapple\n");
#endif //DEBUG_GRAPPLE
            //check if the grapple missile path is clear
            VectorAdd(ms->origin, ms->viewoffset, org);
            trace = AAS_Trace(org, NULL, NULL, reach->end, ms->entitynum, CONTENTS_SOLID);
            VectorSubtract(reach->end, trace.endpos, dir);
            if (VectorLength(dir) > 16)
            {
                result.failure = qtrue;
                return result;
            } //end if
            //activate the grapple
            if (offhandgrapple->value)
            {
                EA_Command(ms->client, cmd_grappleon->string);
            } //end if
            else
            {
                EA_Attack(ms->client);
            } //end else
            ms->moveflags |= MFL_ACTIVEGRAPPLE;
            ms->lastgrappledist = 999999;
        } //end if
        else
        {
            if (dist < 70) speed = 300 - (300 - 4 * dist);
            else speed = 400;
            //
            BotCheckBlocked(ms, dir, qtrue, &result);
            //elemantary action move in direction
            EA_Move(ms->client, dir, speed);
            VectorCopy(dir, result.movedir);
        } //end else
        //if in another area before actually grappling
        areanum = AAS_PointAreaNum(ms->origin);
        if (areanum && areanum != ms->reachareanum) ms->reachability_time = 0;
    } //end else
    return result;
} //end of the function BotTravel_Grapple
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:			-
//===========================================================================
bot_moveresult_t BotTravel_RocketJump(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t hordir;
    float dist, speed;
    bot_moveresult_t result;

    //botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n");
    BotClearMoveResult(&result);
    //
    hordir[0] = reach->start[0] - ms->origin[0];
    hordir[1] = reach->start[1] - ms->origin[1];
    hordir[2] = 0;
    //
    dist = VectorNormalize(hordir);
    //look in the movement direction
    Vector2Angles(hordir, result.ideal_viewangles);
    //look straight down
    result.ideal_viewangles[PITCH] = 90;
    //
    if (dist < 5 &&
            fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 &&
            fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 5)
    {
        //botimport.Print(PRT_MESSAGE, "between jump start and run start point\n");
        hordir[0] = reach->end[0] - ms->origin[0];
        hordir[1] = reach->end[1] - ms->origin[1];
        hordir[2] = 0;
        VectorNormalize(hordir);
        //elemantary action jump
        EA_Jump(ms->client);
        EA_Attack(ms->client);
        EA_Move(ms->client, hordir, 800);
        //
        ms->jumpreach = ms->lastreachnum;
    } //end if
    else
    {
        if (dist > 80) dist = 80;
        speed = 400 - (400 - 5 * dist);
        EA_Move(ms->client, hordir, speed);
    } //end else
    //look in the movement direction
    Vector2Angles(hordir, result.ideal_viewangles);
    //look straight down
    result.ideal_viewangles[PITCH] = 90;
    //set the view angles directly
    EA_View(ms->client, result.ideal_viewangles);
    //view is important for the movment
    result.flags |= MOVERESULT_MOVEMENTVIEWSET;
    //select the rocket launcher
    EA_SelectWeapon(ms->client, (int) weapindex_rocketlauncher->value);
    //weapon is used for movement
    result.weapon = (int) weapindex_rocketlauncher->value;
    result.flags |= MOVERESULT_MOVEMENTWEAPON;
    //
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotTravel_RocketJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_BFGJump(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t hordir;
    float dist, speed;
    bot_moveresult_t result;

    //botimport.Print(PRT_MESSAGE, "BotTravel_BFGJump: bah\n");
    BotClearMoveResult(&result);
    //
    hordir[0] = reach->start[0] - ms->origin[0];
    hordir[1] = reach->start[1] - ms->origin[1];
    hordir[2] = 0;
    //
    dist = VectorNormalize(hordir);
    //
    if (dist < 5 &&
            fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 &&
            fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 5)
    {
        //botimport.Print(PRT_MESSAGE, "between jump start and run start point\n");
        hordir[0] = reach->end[0] - ms->origin[0];
        hordir[1] = reach->end[1] - ms->origin[1];
        hordir[2] = 0;
        VectorNormalize(hordir);
        //elemantary action jump
        EA_Jump(ms->client);
        EA_Attack(ms->client);
        EA_Move(ms->client, hordir, 800);
        //
        ms->jumpreach = ms->lastreachnum;
    } //end if
    else
    {
        if (dist > 80) dist = 80;
        speed = 400 - (400 - 5 * dist);
        EA_Move(ms->client, hordir, speed);
    } //end else
    //look in the movement direction
    Vector2Angles(hordir, result.ideal_viewangles);
    //look straight down
    result.ideal_viewangles[PITCH] = 90;
    //set the view angles directly
    EA_View(ms->client, result.ideal_viewangles);
    //view is important for the movment
    result.flags |= MOVERESULT_MOVEMENTVIEWSET;
    //select the rocket launcher
    EA_SelectWeapon(ms->client, (int) weapindex_bfg10k->value);
    //weapon is used for movement
    result.weapon = (int) weapindex_bfg10k->value;
    result.flags |= MOVERESULT_MOVEMENTWEAPON;
    //
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotTravel_BFGJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_WeaponJump(bot_movestate_t *ms, aas_reachability_t *reach)
{
    vec3_t hordir;
    float speed;
    bot_moveresult_t result;

    VectorClear(result.movedir);

    BotClearMoveResult(&result);
    //if not jumped yet
    if (!ms->jumpreach) return result;
    /*
    //go straight to the reachability end
    hordir[0] = reach->end[0] - ms->origin[0];
    hordir[1] = reach->end[1] - ms->origin[1];
    hordir[2] = 0;
    VectorNormalize(hordir);
    //always use max speed when traveling through the air
    EA_Move(ms->client, hordir, 800);
    VectorCopy(hordir, result.movedir);
    */
    //
    if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed))
    {
        //go straight to the reachability end
        VectorSubtract(reach->end, ms->origin, hordir);
        hordir[2] = 0;
        VectorNormalize(hordir);
        speed = 400;
    } //end if
    //
    EA_Move(ms->client, hordir, speed);
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotFinishTravel_WeaponJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach)
{
    float dist, speed;
    vec3_t hordir;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    //first walk straight to the reachability start
    hordir[0] = reach->start[0] - ms->origin[0];
    hordir[1] = reach->start[1] - ms->origin[1];
    hordir[2] = 0;
    dist = VectorNormalize(hordir);
    //
    BotCheckBlocked(ms, hordir, qtrue, &result);
    speed = 400;
    //elemantary action move in direction
    EA_Move(ms->client, hordir, speed);
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotTravel_JumpPad
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach)
{
    float speed;
    vec3_t hordir;
    bot_moveresult_t result;

    BotClearMoveResult(&result);
    if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed))
    {
        hordir[0] = reach->end[0] - ms->origin[0];
        hordir[1] = reach->end[1] - ms->origin[1];
        hordir[2] = 0;
        VectorNormalize(hordir);
        speed = 400;
    } //end if
    BotCheckBlocked(ms, hordir, qtrue, &result);
    //elemantary action move in direction
    EA_Move(ms->client, hordir, speed);
    VectorCopy(hordir, result.movedir);
    //
    return result;
} //end of the function BotFinishTravel_JumpPad
//===========================================================================
// time before the reachability times out
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BotReachabilityTime(aas_reachability_t *reach)
{
    switch(reach->traveltype & TRAVELTYPE_MASK)
    {
    case TRAVEL_WALK: return 5;
    case TRAVEL_CROUCH: return 5;
    case TRAVEL_BARRIERJUMP: return 5;
    case TRAVEL_LADDER: return 6;
    case TRAVEL_WALKOFFLEDGE: return 5;
    case TRAVEL_JUMP: return 5;
    case TRAVEL_SWIM: return 5;
    case TRAVEL_WATERJUMP: return 5;
    case TRAVEL_TELEPORT: return 5;
    case TRAVEL_ELEVATOR: return 10;
    case TRAVEL_GRAPPLEHOOK: return 8;
    case TRAVEL_ROCKETJUMP: return 6;
    case TRAVEL_BFGJUMP: return 6;
    case TRAVEL_JUMPPAD: return 10;
    case TRAVEL_FUNCBOB: return 10;
    default:
        {
            botimport.Print(PRT_ERROR, "travel type %d not implemented yet\n", reach->traveltype);
            return 8;
        } //end case
    } //end switch
} //end of the function BotReachabilityTime
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotMoveInGoalArea(bot_movestate_t *ms, bot_goal_t *goal)
{
    bot_moveresult_t result;
    vec3_t dir;
    float dist, speed;

#ifdef DEBUG
    //botimport.Print(PRT_MESSAGE, "%s: moving straight to goal\n", ClientName(ms->entitynum-1));
    //AAS_ClearShownDebugLines();
    //AAS_DebugLine(ms->origin, goal->origin, LINECOLOR_RED);
#endif //DEBUG
    BotClearMoveResult(&result);
    //walk straight to the goal origin
    dir[0] = goal->origin[0] - ms->origin[0];
    dir[1] = goal->origin[1] - ms->origin[1];
    if (ms->moveflags & MFL_SWIMMING)
    {
        dir[2] = goal->origin[2] - ms->origin[2];
        result.traveltype = TRAVEL_SWIM;
    } //end if
    else
    {
        dir[2] = 0;
        result.traveltype = TRAVEL_WALK;
    } //endif
    //
    dist = VectorNormalize(dir);
    if (dist > 100) dist = 100;
    speed = 400 - (400 - 4 * dist);
    if (speed < 10) speed = 0;
    //
    BotCheckBlocked(ms, dir, qtrue, &result);
    //elemantary action move in direction
    EA_Move(ms->client, dir, speed);
    VectorCopy(dir, result.movedir);
    //
    if (ms->moveflags & MFL_SWIMMING)
    {
        Vector2Angles(dir, result.ideal_viewangles);
        result.flags |= MOVERESULT_SWIMVIEW;
    } //end if
    //if (!debugline) debugline = botimport.DebugLineCreate();
    //botimport.DebugLineShow(debugline, ms->origin, goal->origin, LINECOLOR_BLUE);
    //
    ms->lastreachnum = 0;
    ms->lastareanum = 0;
    ms->lastgoalareanum = goal->areanum;
    VectorCopy(ms->origin, ms->lastorigin);
    //
    return result;
} //end of the function BotMoveInGoalArea
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags)
{
    int reachnum, lastreachnum, foundjumppad, ent, resultflags;
    aas_reachability_t reach, lastreach;
    bot_movestate_t *ms;
    //vec3_t mins, maxs, up = {0, 0, 1};
    //bsp_trace_t trace;
    //static int debugline;


    BotClearMoveResult(result);
    //
    ms = BotMoveStateFromHandle(movestate);
    if (!ms) return;
    //reset the grapple before testing if the bot has a valid goal
    //because the bot could loose all it's goals when stuck to a wall
    BotResetGrapple(ms);
    //
    if (!goal)
    {
#ifdef DEBUG
        botimport.Print(PRT_MESSAGE, "client %d: movetogoal -> no goal\n", ms->client);
#endif //DEBUG
        result->failure = qtrue;
        return;
    } //end if
    //botimport.Print(PRT_MESSAGE, "numavoidreach = %d\n", ms->numavoidreach);
    //remove some of the move flags
    ms->moveflags &= ~(MFL_SWIMMING|MFL_AGAINSTLADDER);
    //set some of the move flags
    //NOTE: the MFL_ONGROUND flag is also set in the higher AI
    if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND;
    //
    if (ms->moveflags & MFL_ONGROUND)
    {
        int modeltype, modelnum;

        ent = BotOnTopOfEntity(ms);

        if (ent != -1)
        {
            modelnum = AAS_EntityModelindex(ent);
            if (modelnum >= 0 && modelnum < MAX_MODELS)
            {
                modeltype = modeltypes[modelnum];

                if (modeltype == MODELTYPE_FUNC_PLAT)
                {
                    AAS_ReachabilityFromNum(ms->lastreachnum, &reach);
                    //if the bot is Not using the elevator
                    if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR ||
                            //NOTE: the face number is the plat model number
                            (reach.facenum & 0x0000FFFF) != modelnum)
                    {
                        reachnum = AAS_NextModelReachability(0, modelnum);
                        if (reachnum)
                        {
                            //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_plat\n", ms->client);
                            AAS_ReachabilityFromNum(reachnum, &reach);
                            ms->lastreachnum = reachnum;
                            ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach);
                        } //end if
                        else
                        {
                            if (bot_developer->integer)
                            {
                                botimport.Print(PRT_MESSAGE, "client %d: on func_plat without reachability\n", ms->client);
                            } //end if
                            result->blocked = qtrue;
                            result->blockentity = ent;
                            result->flags |= MOVERESULT_ONTOPOFOBSTACLE;
                            return;
                        } //end else
                    } //end if
                    result->flags |= MOVERESULT_ONTOPOF_ELEVATOR;
                } //end if
                else if (modeltype == MODELTYPE_FUNC_BOB)
                {
                    AAS_ReachabilityFromNum(ms->lastreachnum, &reach);
                    //if the bot is Not using the func bobbing
                    if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB ||
                            //NOTE: the face number is the func_bobbing model number
                            (reach.facenum & 0x0000FFFF) != modelnum)
                    {
                        reachnum = AAS_NextModelReachability(0, modelnum);
                        if (reachnum)
                        {
                            //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_bobbing\n", ms->client);
                            AAS_ReachabilityFromNum(reachnum, &reach);
                            ms->lastreachnum = reachnum;
                            ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach);
                        } //end if
                        else
                        {
                            if (bot_developer->integer)
                            {
                                botimport.Print(PRT_MESSAGE, "client %d: on func_bobbing without reachability\n", ms->client);
                            } //end if
                            result->blocked = qtrue;
                            result->blockentity = ent;
                            result->flags |= MOVERESULT_ONTOPOFOBSTACLE;
                            return;
                        } //end else
                    } //end if
                    result->flags |= MOVERESULT_ONTOPOF_FUNCBOB;
                } //end if
                else if (modeltype == MODELTYPE_FUNC_STATIC || modeltype == MODELTYPE_FUNC_DOOR)
                {
                    // check if ontop of a door bridge ?
                    ms->areanum = BotFuzzyPointReachabilityArea(ms->origin);
                    // if not in a reachability area
                    if (!AAS_AreaReachability(ms->areanum))
                    {
                        result->blocked = qtrue;
                        result->blockentity = ent;
                        result->flags |= MOVERESULT_ONTOPOFOBSTACLE;
                        return;
                    } //end if
                } //end else if
                else
                {
                    result->blocked = qtrue;
                    result->blockentity = ent;
                    result->flags |= MOVERESULT_ONTOPOFOBSTACLE;
                    return;
                } //end else
            } //end if
        } //end if
    } //end if
    //if swimming
    if (AAS_Swimming(ms->origin)) ms->moveflags |= MFL_SWIMMING;
    //if against a ladder
    if (AAS_AgainstLadder(ms->origin)) ms->moveflags |= MFL_AGAINSTLADDER;
    //if the bot is on the ground, swimming or against a ladder
    if (ms->moveflags & (MFL_ONGROUND|MFL_SWIMMING|MFL_AGAINSTLADDER))
    {
        //botimport.Print(PRT_MESSAGE, "%s: onground, swimming or against ladder\n", ClientName(ms->entitynum-1));
        //
        AAS_ReachabilityFromNum(ms->lastreachnum, &lastreach);
        //reachability area the bot is in
        //ms->areanum = BotReachabilityArea(ms->origin, ((lastreach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR));
        ms->areanum = BotFuzzyPointReachabilityArea(ms->origin);
        //
        if ( !ms->areanum )
        {
            result->failure = qtrue;
            result->blocked = qtrue;
            result->blockentity = 0;
            result->type = RESULTTYPE_INSOLIDAREA;
            return;
        } //end if
        //if the bot is in the goal area
        if (ms->areanum == goal->areanum)
        {
            *result = BotMoveInGoalArea(ms, goal);
            return;
        } //end if
        //assume we can use the reachability from the last frame
        reachnum = ms->lastreachnum;
        //if there is a last reachability
        if (reachnum)
        {
            AAS_ReachabilityFromNum(reachnum, &reach);
            //check if the reachability is still valid
            if (!(AAS_TravelFlagForType(reach.traveltype) & travelflags))
            {
                reachnum = 0;
            } //end if
            //special grapple hook case
            else if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_GRAPPLEHOOK)
            {
                if (ms->reachability_time < AAS_Time() ||
                        (ms->moveflags & MFL_GRAPPLERESET))
                {
                    reachnum = 0;
                } //end if
            } //end if
            //special elevator case
            else if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR ||
                     (reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB)
            {
                if ((result->flags & MOVERESULT_ONTOPOF_FUNCBOB) ||
                        (result->flags & MOVERESULT_ONTOPOF_FUNCBOB))
                {
                    ms->reachability_time = AAS_Time() + 5;
                } //end if
                //if the bot was going for an elevator and reached the reachability area
                if (ms->areanum == reach.areanum ||
                        ms->reachability_time < AAS_Time())
                {
                    reachnum = 0;
                } //end if
            } //end if
            else
            {
#ifdef DEBUG
                if (bot_developer->integer)
                {
                    if (ms->reachability_time < AAS_Time())
                    {
                        botimport.Print(PRT_MESSAGE, "client %d: reachability timeout in ", ms->client);
                        AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK);
                        botimport.Print(PRT_MESSAGE, "\n");
                    } //end if
                    /*
                    if (ms->lastareanum != ms->areanum)
                    {
                    	botimport.Print(PRT_MESSAGE, "changed from area %d to %d\n", ms->lastareanum, ms->areanum);
                    } //end if*/
                } //end if
#endif //DEBUG
                //if the goal area changed or the reachability timed out
                //or the area changed
                if (ms->lastgoalareanum != goal->areanum ||
                        ms->reachability_time < AAS_Time() ||
                        ms->lastareanum != ms->areanum)
                {
                    reachnum = 0;
                    //botimport.Print(PRT_MESSAGE, "area change or timeout\n");
                } //end else if
            } //end else
        } //end if
        resultflags = 0;
        //if the bot needs a new reachability
        if (!reachnum)
        {
            //if the area has no reachability links
            if (!AAS_AreaReachability(ms->areanum))
            {
#ifdef DEBUG
                if (bot_developer->integer)
                {
                    botimport.Print(PRT_MESSAGE, "area %d no reachability\n", ms->areanum);
                } //end if
#endif //DEBUG
            } //end if
            //get a new reachability leading towards the goal
            reachnum = BotGetReachabilityToGoal(ms->origin, ms->areanum,
                                                ms->lastgoalareanum, ms->lastareanum,
                                                ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries,
                                                goal, travelflags, travelflags,
                                                ms->avoidspots, ms->numavoidspots, &resultflags);
            //the area number the reachability starts in
            ms->reachareanum = ms->areanum;
            //reset some state variables
            ms->jumpreach = 0;						//for TRAVEL_JUMP
            ms->moveflags &= ~MFL_GRAPPLERESET;	//for TRAVEL_GRAPPLEHOOK
            //if there is a reachability to the goal
            if (reachnum)
            {
                AAS_ReachabilityFromNum(reachnum, &reach);
                //set a timeout for this reachability
                ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach);
                //
#ifdef AVOIDREACH
                //add the reachability to the reachabilities to avoid for a while
                BotAddToAvoidReach(ms, reachnum, AVOIDREACH_TIME);
#endif //AVOIDREACH
            } //end if
#ifdef DEBUG

            else if (bot_developer->integer)
            {
                botimport.Print(PRT_MESSAGE, "goal not reachable\n");
                Com_Memset(&reach, 0, sizeof(aas_reachability_t)); //make compiler happy
            } //end else
            if (bot_developer->integer)
            {
                //if still going for the same goal
                if (ms->lastgoalareanum == goal->areanum)
                {
                    if (ms->lastareanum == reach.areanum)
                    {
                        botimport.Print(PRT_MESSAGE, "same goal, going back to previous area\n");
                    } //end if
                } //end if
            } //end if
#endif //DEBUG
        } //end else
        //
        ms->lastreachnum = reachnum;
        ms->lastgoalareanum = goal->areanum;
        ms->lastareanum = ms->areanum;
        //if the bot has a reachability
        if (reachnum)
        {
            //get the reachability from the number
            AAS_ReachabilityFromNum(reachnum, &reach);
            result->traveltype = reach.traveltype;
            //
#ifdef DEBUG_AI_MOVE
            AAS_ClearShownDebugLines();
            AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK);
            AAS_ShowReachability(&reach);
#endif //DEBUG_AI_MOVE
            //
#ifdef DEBUG
            //botimport.Print(PRT_MESSAGE, "client %d: ", ms->client);
            //AAS_PrintTravelType(reach.traveltype);
            //botimport.Print(PRT_MESSAGE, "\n");
#endif //DEBUG
            switch(reach.traveltype & TRAVELTYPE_MASK)
            {
            case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break;
            case TRAVEL_CROUCH: *result = BotTravel_Crouch(ms, &reach); break;
            case TRAVEL_BARRIERJUMP: *result = BotTravel_BarrierJump(ms, &reach); break;
            case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break;
            case TRAVEL_WALKOFFLEDGE: *result = BotTravel_WalkOffLedge(ms, &reach); break;
            case TRAVEL_JUMP: *result = BotTravel_Jump(ms, &reach); break;
            case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break;
            case TRAVEL_WATERJUMP: *result = BotTravel_WaterJump(ms, &reach); break;
            case TRAVEL_TELEPORT: *result = BotTravel_Teleport(ms, &reach); break;
            case TRAVEL_ELEVATOR: *result = BotTravel_Elevator(ms, &reach); break;
            case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break;
            case TRAVEL_ROCKETJUMP: *result = BotTravel_RocketJump(ms, &reach); break;
            case TRAVEL_BFGJUMP: *result = BotTravel_BFGJump(ms, &reach); break;
            case TRAVEL_JUMPPAD: *result = BotTravel_JumpPad(ms, &reach); break;
            case TRAVEL_FUNCBOB: *result = BotTravel_FuncBobbing(ms, &reach); break;
            default:
                {
                    botimport.Print(PRT_FATAL, "travel type %d not implemented yet\n", (reach.traveltype & TRAVELTYPE_MASK));
                    break;
                } //end case
            } //end switch
            result->traveltype = reach.traveltype;
            result->flags |= resultflags;
        } //end if
        else
        {
            result->failure = qtrue;
            result->flags |= resultflags;
            Com_Memset(&reach, 0, sizeof(aas_reachability_t));
        } //end else
#ifdef DEBUG
        if (bot_developer->integer)
        {
            if (result->failure)
            {
                botimport.Print(PRT_MESSAGE, "client %d: movement failure in ", ms->client);
                AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK);
                botimport.Print(PRT_MESSAGE, "\n");
            } //end if
        } //end if
#endif //DEBUG
    } //end if
    else
    {
        int i, numareas, areas[16];
        vec3_t end;

        //special handling of jump pads when the bot uses a jump pad without knowing it
        foundjumppad = qfalse;
        VectorMA(ms->origin, -2 * ms->thinktime, ms->velocity, end);
        numareas = AAS_TraceAreas(ms->origin, end, areas, NULL, 16);
        for (i = numareas-1; i >= 0; i--)
        {
            if (AAS_AreaJumpPad(areas[i]))
            {
                //botimport.Print(PRT_MESSAGE, "client %d used a jumppad without knowing, area %d\n", ms->client, areas[i]);
                foundjumppad = qtrue;
                lastreachnum = BotGetReachabilityToGoal(end, areas[i],
                                                        ms->lastgoalareanum, ms->lastareanum,
                                                        ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries,
                                                        goal, travelflags, TFL_JUMPPAD, ms->avoidspots, ms->numavoidspots, NULL);
                if (lastreachnum)
                {
                    ms->lastreachnum = lastreachnum;
                    ms->lastareanum = areas[i];
                    //botimport.Print(PRT_MESSAGE, "found jumppad reachability\n");
                    break;
                } //end if
                else
                {
                    for (lastreachnum = AAS_NextAreaReachability(areas[i], 0); lastreachnum;
                            lastreachnum = AAS_NextAreaReachability(areas[i], lastreachnum))
                    {
                        //get the reachability from the number
                        AAS_ReachabilityFromNum(lastreachnum, &reach);
                        if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD)
                        {
                            ms->lastreachnum = lastreachnum;
                            ms->lastareanum = areas[i];
                            //botimport.Print(PRT_MESSAGE, "found jumppad reachability hard!!\n");
                            break;
                        } //end if
                    } //end for
                    if (lastreachnum) break;
                } //end else
            } //end if
        } //end for
        if (bot_developer->integer)
        {
            //if a jumppad is found with the trace but no reachability is found
            if (foundjumppad && !ms->lastreachnum)
            {
                botimport.Print(PRT_MESSAGE, "client %d didn't find jumppad reachability\n", ms->client);
            } //end if
        } //end if
        //
        if (ms->lastreachnum)
        {
            //botimport.Print(PRT_MESSAGE, "%s: NOT onground, swimming or against ladder\n", ClientName(ms->entitynum-1));
            AAS_ReachabilityFromNum(ms->lastreachnum, &reach);
            result->traveltype = reach.traveltype;
#ifdef DEBUG
            //botimport.Print(PRT_MESSAGE, "client %d finish: ", ms->client);
            //AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK);
            //botimport.Print(PRT_MESSAGE, "\n");
#endif //DEBUG
            //
            switch(reach.traveltype & TRAVELTYPE_MASK)
            {
            case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break;//BotFinishTravel_Walk(ms, &reach); break;
            case TRAVEL_CROUCH: /*do nothing*/ break;
            case TRAVEL_BARRIERJUMP: *result = BotFinishTravel_BarrierJump(ms, &reach); break;
            case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break;
            case TRAVEL_WALKOFFLEDGE: *result = BotFinishTravel_WalkOffLedge(ms, &reach); break;
            case TRAVEL_JUMP: *result = BotFinishTravel_Jump(ms, &reach); break;
            case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break;
            case TRAVEL_WATERJUMP: *result = BotFinishTravel_WaterJump(ms, &reach); break;
            case TRAVEL_TELEPORT: /*do nothing*/ break;
            case TRAVEL_ELEVATOR: *result = BotFinishTravel_Elevator(ms, &reach); break;
            case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break;
            case TRAVEL_ROCKETJUMP:
            case TRAVEL_BFGJUMP: *result = BotFinishTravel_WeaponJump(ms, &reach); break;
            case TRAVEL_JUMPPAD: *result = BotFinishTravel_JumpPad(ms, &reach); break;
            case TRAVEL_FUNCBOB: *result = BotFinishTravel_FuncBobbing(ms, &reach); break;
            default:
                {
                    botimport.Print(PRT_FATAL, "(last) travel type %d not implemented yet\n", (reach.traveltype & TRAVELTYPE_MASK));
                    break;
                } //end case
            } //end switch
            result->traveltype = reach.traveltype;
#ifdef DEBUG
            if (bot_developer->integer)
            {
                if (result->failure)
                {
                    botimport.Print(PRT_MESSAGE, "client %d: movement failure in finish ", ms->client);
                    AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK);
                    botimport.Print(PRT_MESSAGE, "\n");
                } //end if
            } //end if
#endif //DEBUG
        } //end if
    } //end else
    //FIXME: is it right to do this here?
    if (result->blocked) ms->reachability_time -= 10 * ms->thinktime;
    //copy the last origin
    VectorCopy(ms->origin, ms->lastorigin);
    //return the movement result
    return;
} //end of the function BotMoveToGoal
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotResetAvoidReach(int movestate)
{
    bot_movestate_t *ms;

    ms = BotMoveStateFromHandle(movestate);
    if (!ms) return;
    Com_Memset(ms->avoidreach, 0, MAX_AVOIDREACH * sizeof(int));
    Com_Memset(ms->avoidreachtimes, 0, MAX_AVOIDREACH * sizeof(float));
    Com_Memset(ms->avoidreachtries, 0, MAX_AVOIDREACH * sizeof(int));
} //end of the function BotResetAvoidReach
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotResetLastAvoidReach(int movestate)
{
    int i, latest;
    float latesttime;
    bot_movestate_t *ms;

    ms = BotMoveStateFromHandle(movestate);
    if (!ms) return;
    latesttime = 0;
    latest = 0;
    for (i = 0; i < MAX_AVOIDREACH; i++)
    {
        if (ms->avoidreachtimes[i] > latesttime)
        {
            latesttime = ms->avoidreachtimes[i];
            latest = i;
        } //end if
    } //end for
    if (latesttime)
    {
        ms->avoidreachtimes[latest] = 0;
        if (ms->avoidreachtries[i] > 0) ms->avoidreachtries[latest]--;
    } //end if
} //end of the function BotResetLastAvoidReach
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotResetMoveState(int movestate)
{
    bot_movestate_t *ms;

    ms = BotMoveStateFromHandle(movestate);
    if (!ms) return;
    Com_Memset(ms, 0, sizeof(bot_movestate_t));
} //end of the function BotResetMoveState
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotSetupMoveAI(void)
{
    BotSetBrushModelTypes();
    sv_maxstep = LibVar("sv_step", "18");
    sv_maxbarrier = LibVar("sv_maxbarrier", "32");
    sv_gravity = LibVar("sv_gravity", "800");
    weapindex_rocketlauncher = LibVar("weapindex_rocketlauncher", "5");
    weapindex_bfg10k = LibVar("weapindex_bfg10k", "9");
    weapindex_grapple = LibVar("weapindex_grapple", "10");
    entitytypemissile = LibVar("entitytypemissile", "3");
    offhandgrapple = LibVar("offhandgrapple", "1");
    cmd_grappleon = LibVar("cmd_grappleon", "+button11");
    cmd_grappleoff = LibVar("cmd_grappleoff", "-button11");
    return BLERR_NOERROR;
} //end of the function BotSetupMoveAI
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotShutdownMoveAI(void)
{
    int i;

    for (i = 1; i <= MAX_CLIENTS; i++)
    {
        if (botmovestates[i])
        {
            FreeMemory(botmovestates[i]);
            botmovestates[i] = NULL;
        } //end if
    } //end for
} //end of the function BotShutdownMoveAI


